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
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 :
staticcall(gas(), _a,28,32,0x00,0x20)
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
// function to call: "73712595": "revertWith999()",functionrevertWith999() externalpurereturns (uint256) {assembly {mstore(0x00,999)revert(0x00,0x20) } }// Yul calling function functiongetViaRevert(address_a) externalviewreturns (uint256) {assembly {mstore(0x00,0x73712595)pop(staticcall(gas(), _a,28,32,0x00,0x20)) // discard the return boolean value since we know it will be 0 / falsereturn(0x00,0x20) // returns 999 } }
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
// function to call:"196e6d84": "multiply(uint128,uint16)",functionmultiply(uint128_x,uint16_y) externalpurereturns (uint256) {return _x * _y; }// Yul calling function functioncallMultiply(address_a) externalviewreturns (uint256 result) {assembly {let mptr :=mload(0x40)let oldMptr := mptrmstore(mptr,0x196e6d84)mstore(add(mptr,0x20),3) // _x mstore(add(mptr,0x40),11) // _y mstore(0x40,add(mptr,0x60)) // advance the memory pointer 3 x 32 bytes// 00000000000000000000000000000000000000000000000000000000196e6d84// 0000000000000000000000000000000000000000000000000000000000000003// 000000000000000000000000000000000000000000000000000000000000000blet success :=staticcall(gas(), _a,add(oldMptr,28),// fast forward to get the function signature mload(0x40),// advanced memory pointer after arguments have been stored in memory 0x00,0x20 )ifiszero(success) {revert(0,0) } result :=mload(0x00) } }
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
// function to call : "4018d9aa": "setX(uint256)"functionsetX(uint256_x) external { x = _x; }// Yul calling function functionexternalStateChangingCall(address_a) external {assembly {mstore(0x00,0x4018d9aa)mstore(0x20,999)// memory now looks like this// 0x000000000000000000000000000000000000000000000000000000004018d9aa// 000000000000000000000000000000000000000000000000000000000000000999let success :=call(gas(), _a,callvalue(),28,add(28,32),0x00,0x00 )ifiszero(success) {revert(0,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
function_delegate(address implementation) internalvirtual {assembly {// Copy msg.data. We take full control of memory in this inline assembly// block because it will not return to Solidity code. We overwrite the// Solidity scratch pad at memory position 0.calldatacopy(0,0,calldatasize())// Call the implementation.// out and outsize are 0 because we don't know the size yet.// msg.value: hardcoded to zero let result :=delegatecall(gas(), implementation,0,calldatasize(),0,0)// Copy the returned data.returndatacopy(0,0,returndatasize()) switch result// delegatecall returns 0 on error. case 0 {revert(0,returndatasize()) } default {return(0,returndatasize()) } } }