Solidity delegatecall vs. Yul delegatecall

Hi Andrew @abcoathup,

I trust that you are well.

According to the OpenZeppelin explanation of proxy contracts (https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies), Yul assembly code needs to be used for the following reason:

The technique needs to be implemented using Yul because Solidity’s delegatecall returns a boolean instead of the callee’s return data.

assembly {
  let ptr := mload(0x40)

  // (1) copy incoming call data
  calldatacopy(ptr, 0, calldatasize)

  // (2) forward call to logic contract
  let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
  let size := returndatasize

  // (3) retrieve return data
  returndatacopy(ptr, 0, size)

  // (4) forward return data back to caller
  switch result
  case 0 { revert(ptr, size) }
  default { return(ptr, size) }
}

However, delegatecall as a member of the address type returns a success condition boolean and return data. (https://solidity.readthedocs.io/en/v0.7.4/units-and-global-variables.html#index-6)

.delegatecall(bytes memory) returns (bool, bytes memory) issue low-level DELEGATECALL with the given payload, returns success condition and return data, forwards all available gas, adjustable

What am I missing? Cheers.

Sincerely,
Craig

2 Likes

Hi @cjd9s,

Sorry for the delay in responding. I will have to find out why this is.

Hi @cjd9s,

I am still working on my understanding of this and will come back to you.

Hi Andrew @abcoathup,

Much appreciated. Cheers.

Sincerely,
Craig

1 Like

Hi @cjd9s,

Since Solidity 0.5, delegatecall provides access to the return data:

The proxy was written prior to Solidity 0.5.

Some assembly is still required as the fallback function can’t have a return value, so Solidity won’t allow us to return anything.

The proxy could potentially be changed to only use assembly for the return statement. Using assembly for the entire block may be cheaper but the gas costs haven’t been measured.

1 Like

Hi Andrew @abcoathup,

Excellent answer. Makes perfect sense. Cheers.

Sincerely,
Craig

1 Like

I don't think that this is the case anymore. As per the docs for Solidity v0.8.7:

A contract can have at most one fallback function, declared using either

  • fallback () external [payable] or
  • fallback (bytes calldata _input) external [payable] returns (bytes memory _output)

It is however true that the receive ether function can't have a return type. And since receive is used in OpenZeppelin's Proxy.sol, assembly is still required.

At any rate, I would love to see the _delegate implementation use assembly only for the return statement. The Solidity-level delegatecall is much more palatable.

1 Like