9

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?

3
  • 1
    You might want to contribute to the discussion going on github.com/ethereum/EIPs/pull/897 Commented Mar 1, 2018 at 23:04
  • 1
    thank you for this great example code. I don't have an answer to your quesiton but wondered if you could help me? I'd like to use this method but I only care about one specific function. When using delegatecall would I specify the function explicitly or will it be included in msg.data? if I do it explicitly would it look like this? gist.github.com/okwme/a86e32a843bc15453002c5a0229da021 Commented Mar 22, 2018 at 14:43
  • Looks like Anton Bukov added support for high-level delegatecall in OpenZeppelin's Address.sol. Commented Aug 30, 2021 at 16:36

2 Answers 2

3

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) 
3
  • Why does @willjgriff's code stop working for return sizes bigger than 64 bytes? Commented Aug 30, 2021 at 16:11
  • Is that because it would override the free memory pointer? Commented Aug 30, 2021 at 17:03
  • 1
    Yes, and writing further would clobber any other memory that has been allocated. See docs.soliditylang.org/en/v0.8.7/internals/layout_in_memory.html Commented Aug 30, 2021 at 19:05
1

To mitigate the issue mentioned by @Tjaden Hess, do what OpenZeppelin does:

function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); if (success) { return returndata; } else { if (returndata.length > 0) { assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } 

If the call does not fail, return the data as you would normally do in Solidity. Otherwise, revert via assembly to bubble up the revert reason.

Pro tip: see my implementation of this in PRBProxy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.