staticcall
The difference between staticcall
and delegatecall
is that staticcall
makes calls to only view
or pure
functions. A staticcall
will revert if it makes a call to a function in a smart contract that changes the state of that smart contract.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract CalledContract {
struct Data {
uint128 x;
uint128 y;
}
string public myString;
uint256 public bigSum;
fallback() external {}
function add(uint256 i) public pure returns (uint256) {
return i + 78;
}
function multiply(uint8 i, uint8 j) public pure returns (uint256) {
return uint256(i * j);
}
function arraySum(uint256[] calldata arr) public pure returns (uint256) {
uint256 len = arr.length;
uint256 sum;
for (uint256 i; i != len; i++ ) {
sum = sum + arr[i];
}
return sum;
}
function setString(string calldata str) public {
if (bytes(str).length > 31) revert();
myString = str;
}
function structCall(Data memory data) public {
bigSum = uint256(data.x + data.y);
}
}
contract CallerContract {
address public _calledContract;
constructor() {
_calledContract = address(new CalledContract());
}
/// @notice add: 1003e2d2.
function callAdd(uint256 num) public view returns (uint256) {
address calledContract = _calledContract;
assembly {
mstore(0x80, 0x1003e2d2)
mstore(0xa0, num)
let success := staticcall(gas(), calledContract, 0x9c, 0x24, 0x00, 0x00)
if iszero(success) {
revert (0x00, 0x00)
}
returndatacopy(0x80, 0x00, returndatasize())
return(0x80, returndatasize())
}
}
/// @notice multiply: 6a7a8e0b.
function callMultiply(uint8 num1, uint8 num2) public view returns (uint256) {
address calledContract = _calledContract;
assembly {
mstore(0x80, 0x6a7a8e0b)
mstore(0xa0, num1)
mstore(0xc0, num2)
let success := staticcall(gas(), calledContract, 0x9c, 0x44, 0x00, 0x00)
if iszero(success) {
revert (0x00, 0x00)
}
returndatacopy(0x80, 0x00, returndatasize())
return(0x80, returndatasize())
}
}
/// @notice arraySum: 7c2b11cd.
function callArraySum(
uint256 num1,
uint256 num2,
uint256 num3,
uint256 num4
) public view returns (uint256)
{
address calledContract = _calledContract;
assembly {
mstore(0x80, 0x7c2b11cd)
mstore(0xa0, 0x20)
mstore(0xc0, 0x04)
mstore(0xe0, num1)
mstore(0x0100, num2)
mstore(0x0120, num3)
mstore(0x0140, num4)
let success := staticcall(gas(), calledContract, 0x9c, 0xc4, 0x00, 0x00)
if iszero(success) {
revert (0x00, 0x00)
}
returndatacopy(0x80, 0x00, returndatasize())
return(0x80, returndatasize())
}
}
/// @notice setString: 7fcaf666.
function callSetString(string calldata str) public {
uint8 len = uint8(bytes(str).length);
if (len > 31) revert();
address calledContract = _calledContract;
bytes memory strCopy = bytes(str);
assembly {
mstore(0x0200, mload(add(strCopy, 0x20)))
mstore(0x80, 0x7fcaf666)
mstore(0xa0, 0x20)
mstore(0xc0, len)
mstore(0xe0, mload(0x0200))
let success := call(gas(), calledContract, 0, 0x9c, 0x64, 0x00, 0x00)
}
}
function getNewString() public view returns (string memory) {
return CalledContract(_calledContract).myString();
}
function callStructCall(uint128 num1, uint128 num2) public {
address calledContract = _calledContract;
bytes4 _selector = CalledContract.structCall.selector;
assembly {
mstore(0x9c, _selector)
mstore(0xa0, num1)
mstore(0xc0, num2)
let success := call(gas(), calledContract, 0, 0x9c, 0x44, 0x00, 0x00)
}
}
function getBigSum() public view returns (uint256) {
return CalledContract(_calledContract).bigSum();
}
}
returndatacopy(a, b, c)
copies the data returned from a function call to location a
in memory. But that's not all, it tells the EVM to copy starting from b
and copy as much as c
in bytes. In other words, "Start from position b
in the returned data and copy c
bytes of the returned data to memory location a
.".
returndatasize()
returns the size of the data returned from a function call.