Overview
Call and staticcall are members of address type
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