4

I'm a bit confused by the code at this smart contract, which is part of the Aragon project code:

function delegatedFwd(
    address _dst,
    bytes _calldata,
    uint256 _minReturnSize) internal {

    require(isContract(_dst));
    uint256 size;
    uint256 result;
    uint256 fwd_gas_limit = FWD_GAS_LIMIT;
    assembly {
        result := delegatecall(sub(gas, fwd_gas_limit), _dst, add(_calldata, 0x20), mload(_calldata), 0, 0)
        size := returndatasize
    }

    require(size >= _minReturnSize);

    assembly {
        let ptr := mload(0x40)
        returndatacopy(ptr, 0, size)

        // revert instead of invalid() bc if the underlying call failed with invalid() it already wasted gas.
        // if the call returned error data, forward it
        switch result case 0 { revert(ptr, size) }
        default { return(ptr, size) }
    }
}

The function declaration does not declare a return value, yet the function itself appears in the last line to return data.

If a caller called this function, wouldn't the caller have to ignore the return value or the code should fail to compile?

What am I missing? Would the caller have to access the return values with assembly and if that's the case isn't this violating the spirit of the function definition? It seems like bad programming practice to me.

  • Comments are not for extended discussion; this conversation has been moved to chat. – eth May 27 '18 at 2:04
2

That's the assembly return instruction, which returns from the whole contract execution not just the function (reference):

return(p, s) | end execution, return data mem[p..(p+s))

In your case, return(ptr, size) ends the contract execution and returns the return value of the delegatecall() before.

On assembly level, you can pretty much do anything you want as long as it's allowed by the EVM. Restrictions of solidity don't apply there (which means that you can easily break the whole contract with faulty assembly).

Obviously, the caller of the function in the same contract cannot access the return data since he is not executed after his call. On the other hand, the caller of the contract may access the return value, although he either needs to use assembly to process it or use a modified contract interface for the called contract.

1

The function modifies the values at memory level using assembly, this memory modification allows the method to edit the portion of memory where the return values are stored.

This function is the implementation of the Proxy pattern and uses a delegate forward to execute/load the code of another contract.

For example:

  • ContractA uses a proxy to resolve the function:

    function methodOne() public view returns(uint256);

  • ContractB has the actual implementation of methodOne()

  • ProxyContract uses a delegate forward to load the code of another contract and the result of that code execution.

When the function is called then:

  1. ContractA invokes the proxy to execute the implementation of methodOne() (from ContractB)
  2. The ProxyContract loads the code from ContractB and execute methodOne() and save the result of the execution in memory
  3. Finally ContractA.methodOne() loads the result from memory and returns the values.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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