Solidity Call & Staticcall

Solidity Call & Staticcall

Overview

Call and staticcall are members of address type

call&staticcall.png

These are low level functions to interact with other contracts,where staticcall can only be used to call read-only functions(pure or view functions)

However it is not the recommend way to call existing functions.

Sending Ether using call

One of the most common use cases of call is to send Ether

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Call{
    function sendEth(address _to) external payable  returns(bool){
         (bool success, ) = _to.call{value: msg.value}("");
         return success;
    }
}

address _to can be address of a contract that implemented fallback() function or just an account address

Calling functions of other contract with address & function name

Consider this contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Receiver{
    address public s_someAddress;
    uint256 public s_amount;
    function transfer(address someAddress, uint256 amount) public {
        s_someAddress = someAddress;
        s_amount = amount;
    }

}

Using encodeWithSelector

There are 4 steps to follow

1. Get function signature

Function signature is the function name with the parameter types here,it is

transfer(address,uint256)

2. Get function selector from it

Function selector is first 4 bytes of

keccak256(bytes("transfer(address,uint256)"))

We can get it by

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Caller{
    function getFunctionSelector() public pure returns (bytes4 selector) {
        selector = bytes4(keccak256(bytes("transfer(address,uint256)")));
    }
}

3. Cook the bytes data using encodeWithSelector (which will be used as argument in call)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Caller{
    function getFunctionSelector() public pure returns (bytes4 selector) {
        selector = bytes4(keccak256(bytes("transfer(address,uint256)")));
    }
    function getEncodedData(address _someAddress,uint256 _amount) public pure returns(bytes memory){
        return abi.encodeWithSelector(getFunctionSelector(),_someAddress,_amount);
    }
}

4. Call the function using Receiver contract address

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Caller{
    function getFunctionSelector() public pure returns (bytes4 selector) {
        selector = bytes4(keccak256(bytes("transfer(address,uint256)")));
    }
    function getEncodedData(address _someAddress,uint256 _amount) public pure returns(bytes memory){
        return abi.encodeWithSelector(getFunctionSelector(),_someAddress,_amount);
    }
    function callTransfer(address _someAddress,uint256 _amount,address receiver) public returns(bool){
        (bool success,) = receiver.call(getEncodedData(_someAddress,_amount));
        return success;
    }
}

Using encodeWithSignature

Here ,we can skip finding Function selector part

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Caller{
    function getEncodedData(address _someAddress,uint256 _amount) public pure returns(bytes memory){
        return abi.encodeWithSignature("transfer(address,uint256)",_someAddress,_amount);
    }
    function callTransfer(address _someAddress,uint256 _amount,address receiver) public returns(bool){
        (bool success,) = receiver.call(getEncodedData(_someAddress,_amount));
        return success;
    }
}

Calling Read-only functions using Staticcall

Consider this contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Receiver{
    function add(uint x,uint y) external pure returns(uint){
        return(x+y);
    }
}

Since add(uint,uint) is a pure function, we can use staticcall to interact with it

Syntax is just like call function

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Caller{
    function getEncodedData(uint256 _x,uint256 _y) public pure returns(bytes memory){
        return abi.encodeWithSignature("add(uint256,uint256)",_x,_y);
    }
    function callStatic(address receiver,uint256 _x,uint256 _y) public view returns(bool,bytes memory){
        (bool success,bytes memory data) = receiver.staticcall(getEncodedData(_x,_y));
        return (success,data);
    }
}

We'll get uint output in bytes form

References