[Ethereum] Using a high level delegate call in upgradable contracts since Byzantium

contract-designcontract-developmentproxy-contractssolidityupgrading

Since Byzantium we can implement upgradable proxy contracts much easier with the use of returndatacopy and returndatasize assembly instructions. This means we no longer have to register return types and sizes like when using the EtherRouter.

The most reliable way we know how to structure a proxy contract is like the Zeppelin Proxy where the delegatecall is made in assembly. However, it also seems to work when doing the delegatecall as a high level Solidity call where the proxy contract fallback function looks like this instead:

function () public {

    bool callSuccess = upgradableContractAddress.delegatecall(msg.data);

    if (callSuccess) {
        assembly {
            returndatacopy(0x0, 0x0, returndatasize)
            return(0x0, returndatasize)
        }
    } else {
        revert();
    }
}

This approach (see the whole proxy here) is a bit more succinct and requires less knowledge of assembly to understand. My minimal tests for this approach seem to work.

So in what situations will this high level approach not work?

And if there aren't any, how likely is it that the compiled bytecode for the high-level delegatecall will change between versions of Solidity, breaking this approach for those versions?

Best Answer

One issue is that you copy your data into memory starting at address 0. This will work for return sizes less than 64 bytes, but will start overwriting other memory at that point.

Instead you should do something more like

let m := mload(0x40)
returndatacopy(m, 0, returndatasize)
return(0, returndatasize)
Related Topic