Solidity Functions – Returning Values from Non-Return Functions in Solidity

assemblysolidity

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.

Best Answer

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.

Related Topic