0

There's 2 contracts A and B, A imports B, where B is a library that can be upgraded. To achieve this, I tried replacing B with a proxy contract and a delegate contract.

We now have contracts Foo, which imports proxy contract Bar, pointing to delegate contract ZeroDelegate.

Foo.sol

pragma solidity ^0.4.18;

import './Bar.sol';

contract Foo {
    uint storageData;
    Bar bar;
    address barContractAddress;

    constructor(address _barContractAddress) public {
        barContractAddress = _barContractAddress;
    }

    function set(uint x) public {
        storageData = x;
    }

    function get() view public returns (uint) {
        return storageData;
    }

    function baz() public returns (uint) {
        bar = Bar(barContractAddress);
        storageData = bar.baz(storageData);
    }

}

Bar.sol

pragma solidity ^0.4.18;

import './Proxy.sol';

contract Bar is Proxy {
    function baz(uint x) public returns (uint) {
        return  x * x;
    }
}

ZeroDelegate.sol

pragma solidity ^0.4.18;

contract ZeroDelegate {
    function baz(uint x) public returns (uint) {
        return x * 0;
    }
}

Proxy.sol

pragma solidity ^0.4.18;

import "zeppelin-solidity/contracts/ownership/Ownable.sol";

contract Proxy is Ownable {

    event Upgraded(address indexed implementation);

    address internal _implementation;

    function implementation() public view returns (address) {
        return _implementation;
    }

    function upgradeTo(address impl) public onlyOwner {
        require(_implementation != impl);
        _implementation = impl;
        emit Upgraded(impl);
    }

    function () payable public {
        address _impl = implementation();
        require(_impl != address(0));
        bytes memory data = msg.data;

        assembly {
            let result := delegatecall(gas, _impl, add(data, 0x20), mload(data), 0, 0)
            let size := returndatasize
            let ptr := mload(0x40)
            returndatacopy(ptr, 0, size)
            switch result
            case 0 { revert(ptr, size) }
            default { return(ptr, size) }
        }
    }
}

Now we first deploy the contracts Foo, Bar, ZeroDelegate.

bar = await Bar.new()
foo = await Foo.new(bar.address)
zeroDelegate = await ZeroDelegate.new()

And foo.baz() sqaures the number 2 to 4.

x = await foo.baz()
console.log(x.toNumber())  // 4

Next we upgrade the Bar contract to ZeroDelegate, but foo.baz() still squares the number 4 to 16

await bar.upgradeTo(zeroDelegate.address)
bar = _.extend(bar, ZeroDelegate.at(bar.address))
await foo.baz()
x = await foo.get()
console.log(x.toNumber())  // 16, but expects 0

However if we were to re-deploy Foo contract, it uses the upgraded Bar. Why is this, and how can we let Foo use the upgraded Bar functions without having to re-deploy Foo, which kind of defeats the purpose of using an upgradable contract.

foo = await Foo.new(bar.address)
await foo.baz()
x = await foo.get()
console.log(x.toNumber())  // 0

1 Answer 1

1

You need to make your fallback function called baz(), then treat Bar.sol the same way you treat ZeroDelegate (as a non-inherited external contract—maybe actually library). The delegated call will be sent to that external contract (whichever _imp is currently pointing to) and use the logic within that contract. When update _imp to the address of Bar or ZeroDelegate to switch it's behavior.

Here's a working example with an upgradeable TokenURI function:
https://github.com/clovers-network/clovers-contracts/blob/master/contracts/Clovers.sol
pointing to:
https://github.com/clovers-network/clovers-contracts/blob/master/contracts/CloversMetadata.sol

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.