3

To create an upgradeable contract I have to use multiple proxies. The contract applies to Proxy A, then Proxy A applies to Proxy B. But delegatecall doesn't work in this case.

The simplified code of my contracts:

pragma solidity ^0.4.24;


contract A {
    uint256 public value = 100;

    function mul() public {
        value *= 2;
    }
}

contract B {
    uint256 public value = 200;
    address a;

    constructor(address _a) {
        a = _a;
    }

    function delegate() public {
        a.delegatecall(bytes4(keccak256("mul()")));
    }
}

contract C {
    uint256 public value = 500;
    address b;

    constructor(address _b) {
        b = _b;
    }

    function delegate() public {
        b.delegatecall(bytes4(keccak256("delegate()")));
    }
}

delegate() function works only if it called at contract B

Thanks!

2 Answers 2

9

When, in C, you do b.delegatecall(, what happens is that:

  • the code used is that of B
  • the storage used is that of C

And B and C have the same storage layout:

  • both the uint value are located on storage slot 0.
  • with address a and address b located on storage slot 1

So now you are executing B's code with C's storage. And you ask a.delegatecall(bytes4(keccak256("mul()")));. What the underlying bytecode does is actually take C's value at storage slot 1, in effect b, and use that as a. So you are about to delegatecall on B again.

And you call mul() on B? There is no such function, so it does nothing. In fact, depending on your version of Solidity, this call would silently fail or not.

It's working as expected :)

1
  • Thanks, your answer is very comprehensive. I wonder if it is possible to achieve what Mikky want to do. Commented Aug 15, 2018 at 14:55
0

When you do a delegatecall the code of the called contract is used with the calling contract state.

So in the first delegatecall you are calling B contract code with C contract state. In the second delegatecall you are calling A contract code with C contract state.

So you cannot do a simple nested proxy in a naive way.

If you want to solve this, you should first check if the inner proxy is in fact a proxy, meaning that it has an delegate address set (in your example address a) And if so set the C delegate address to be B delegate address, delegate the call to b (the original C delegate address) and then revert the change of the delegate address of contract C.

pragma solidity ^0.4.24;


contract A {
    uint256 public value = 100;

    function mul() public {
        value *= 2;
    }
}

contract B {
    uint256 public value = 200;
    address a;

    constructor(address _a) {
        a = _a;
    }

    function delegate() public {
        a.delegatecall(bytes4(keccak256("mul()")));
    }
}

contract C {
    uint256 public value = 500;
    address b;

    constructor(address _b) {
        b = _b;
    }

    function delegate() public {
        address orgB = b;
        if (b.a != address(0)) {
            b = b.a;
        }
        orgB.delegatecall(bytes4(keccak256("delegate()")));
        b = orgB;
    }
}

For more on that, you can see how to update a proxy to a nested proxy: 06a396bd

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.