An example of the `STATICCALL` opcode usage
Let's check an example in production code where the `STATICCALL` opcode introduced in EIP-214 is used
Let’s pick the following code snippet from Uniswap V3 Pool - balance0()
function balance0() private view returns (uint256) {
(bool success, bytes memory data) =
token0.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this)));
require(success && data.length >= 32);
return abi.decode(data, (uint256));
}
What it does is to get the Token0 Pool’s balance
This is typically done by running something like
IERC20(token0).balanceOf(address(this));
however, this is not the case here as the authors make use of `staticcall()` approach
What does it mean and why so?
Under the hood, it makes use of the `STATICCALL` opcode that has been introduced in EIP-214 and its function is to provide the runtime guarantee that no state change can happen within the given call
This is a more powerful guarantee than the one provided by declaring a function as `view` or `pure` as afaik atm the compiler uses these annotations only to run compile-time checks about the state modifications, but these checks can not be run on code that is not readable by the compiler, for example in the case of external cotracts
So how can “Pepe the solidity coder“ get guarantees about the state changes in third parties contracts, which he needs to call?
This is why the `STATICCALL` opcode has been added: to run runtime checks about the state modifications, so that applies to all the code that is actually run and not just to the code that Pepe has compiled
So how is this implemented?
As explained in the EIP-214 the mechanism relies on adding a new boolean flag to the EVM called `STATIC`
When `STATIC=false` → State Modification is allowed
When `STATIC=true` → State Modification is not allowed, it means any attempt to do so results in an exception
As per EIP
Any opcode that attempts to perform such a modification (see below for details) will result in an exception instead of performing the modification.
So initially the flows of execution starts with `STATIC=false`
When `STATICCALL` is found then
the current value of the `STATIC` flag is saved in the stack
then `STATIC=true`
finally the call is executed
When the call returns, the previous value of `STATIC` is restored
Note this mechanism also covers multiple `STATICCALL` nested in each other: for each nested one the value of `STATIC` will simply be restored to `true` and only when the last `STATICCALL` returns then it is stored to `false` that is the initial value
The UniswapV3 Pool uses this approach as it has to interact with external contracts it has no knowledge about, so calling their functions is sort of arbitrary code execution and hence the more the checks the better
The assumption they are ERC20-compatible contracts has to be be made and as such calling `balanceOf()`should produce no state changes
So using `STATICCALL` to call `balanceOf()` helps preventing malicious ERC20 behaviors