External Calls

STATICCALL

contract SolidityContract {
	// "9a884bde": "get21()" - function signature 
    function get21() external pure returns (uint256) {
        return 21;
    }
} 
  • In Yul, we have to load the function selector into memory, then point to the region that will be part of our abi call, i.e. bytes 28 to 32 which contain the function signature

function externalViewCallNoArgs(address _a)
        external
        view
        returns (uint256)
    {
        assembly {
            mstore(0x00, 0x9a884bde)
            // 000000000000000000000000000000000000000000000000000000009a884bde
            //                                                         |       |
            //                                                         28      32
            let success := staticcall(gas(), _a, 28, 32, 0x00, 0x20)
            if iszero(success) {
                revert(0, 0)
            }
            return(0x00, 0x20)
        }
    }
  • Opcodes for making external calls: staticcall, call, and delegatecall

  • If we are inside of a view function, we have to use staticcall, because static calls do not change state. if state is being changed, the call will revert

  • The compiler will not allow us to use call within a view function, since the function is not supposed to modify state

  • Breakdown of arguments in staticcall :

    • gas() - amount of remaining gas the contract has left. can be hardcoded to a smaller amount, can be used if we don’t trust the receiving contract, since it may attempt to consume all the gas in a DoS attack

    • _a - address of the contract we are calling

    • 28, 32 - intended [tx.data](<http://tx.data>) , which is what we have loaded into memory

    • 0x00, 0x20 - region in memory that we are going to copy the results back into. so when the function returns, it will override the function selector in 0x00, but it does not matter since we don’t need this info any more

Getting return values from a revert call in Yul

  • when a function reverts, it will still return the revert values to the same location that we assigned to it, in this case 0x00 - 0x20

Calling a function that includes arguments

  • since the arguments take up more than 64 bytes, we cant put it in scratch space

  • have to store the function arguments into locations in memory with mstore

CALL

  • Difference from staticcall is the addition of a callvalue() variable, which is used to forward the ETH received as part of the transaction (msg.value )

  • If a function is not payable, callvalue() can just be set to 0

Unknown return size

  • in some situations, we don’t know what the size of the return value will be

  • if the function returns some unknown data size, e.g. a string or an array, we use returndatasize() to obtain the return data size, and ignore the allocated positions in staticcall

  • returndatacopy will copy the return value into a location in memory: in this case, copy into slot 0, the data at 0, up to the total size of the return data

DELEGATECALL

  • Openzeppelin implementation of delegatecall

Last updated