EIP214 — STATIC CALL IN YUL AND USAGE WITH PRECOMPILES.

Paul Elisha
5 min readMar 25, 2024

--

Prerequisites:

. Understanding of Solidity for smart contract development.

. Understanding of Yul for writing low-level smart contract.

What is Staticcall?

The static call is like a regular Klaytn call except that it reverts if a state change happens. It cannot be used to transfer Ether, it can only be used to call read-only functions(pure or view functions). Both the KLVM opcode, the Yul assembly function, and the built-in solidity function are named static call.

Staticcall was introduced in EIP 214 added to Ethereum in 2017 as part of the Byzantium hard fork and since Klaytn is Ethereum compatible, it works for smart contracts deployed on the Klaytn chain. For functions where only a return value is desired and state changes are unwanted, it adds a layer of safety. Staticcall will revert if the contract called causes a state change which includes: logging an event, sending ether, creating a contract, destroying a contract, or setting a storage variable. Reading a storage variable is ok. A call that does not transfer ether is allowed as long as it doesn’t result in a state change.

Let’s take a quick view at the summary and motivation of EIP214

Summary:

To increase smart contract security, this proposal adds a new opcode that can be used to call another contract (or itself) while disallowing any modifications to the state during the call (and its subcalls, if present).

Motivation:

Currently, there is no restriction about what a called contract can do, as long as the computation can be performed with the amount of gas provided. This poses certain difficulties about smart contract engineers; after a regular call, unless you know the called contract, you cannot make any assumptions about the state of the contracts. Furthermore, because you cannot know the order of transactions before they are confirmed by miners, not even an outside observer can be sure about that in all cases.

This EIP adds a way to call other contracts and restrict what they can do in the simplest way. It can be safely assumed that the state of all accounts is the same before and after a static call.

Staticcall behaves quite differently from call and delegatecall. While the former two enable execution and potential state modifications, staticcall is a silent observer.

Staticcall is an ideal choice when you want to retrieve information from another contract without affecting its state. This can be valuable for various use cases, such as fetching the current price from an oracle contract or verifying certain conditions without altering the blockchain’s state.

Note: Limit Staticcall Usage: Use staticcall only when you need to read data from a contract without any intention of modifying its state. If you’re unsure about a contract’s behavior, opt for call or delegatecall.

USAGE WITH VIEW FUNCTIONS:

Solidity compiles view functions that call external contracts to use staticcall under the hood.

Here is a minimal example:

contract ERC20User { 
IERC20 public token; // rest of the code
. function myBalance() public view returns (uint256 balance {
balance = token.balanceOf(address(this));
}

function myBalanceLowLevelEquivalent() public view returns (uint256 balance) {
(bool ok, bytes memory result) = token.staticcall(abi.encodeWithSignature("balanceOf(address)", address(this)));
require(ok);
balance = abi.decode(result, (uint256));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Receiver{
function add(uint x,uint y) external pure returns(uint){
return(x+y);
}
}

// 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);
}
}

Note: If balanceOf changes the state, the code will revert.

USAGE WITH PRECOMPILED SMART CONTRACTS:

Precompiled contracts are contracts native to the platform implementation with useful functionalities.

Staticcall is the appropriate way to interact with Klaytn precompiled contracts (addresses 0x01 to 0x0A) as none of them are state-changing.

The following example will compute the SHA256 of a uint. Note that this precompile hashes the data directly, and it should not be abi encoded.

function getSha256(uint256 x) public view returns (bytes32 hashOut) {
(bool ok, bytes memory result) = address(0x02).staticcall(abi.encode(x));
require(ok);

hashOut = abi.decode(result, (bytes32));
}
In addition to the addresses on Ethereum, Klaytn took the initiative to make changes to the precompiled contracts at the release of the Klaytn v1.8.0. implements precompiled contracts to support new features from 0x3FD to 0x3FF.

In addition, with the release of Klaytn v1.8.0, Klaytn took the initiative to implement precompiled contracts from addresses 0x3FD to 0x3FF to support new features peculiar to Klaytn to prevent overlapping.

Contracts deployed before the istanbul EVM hardfork should use the original addresses.

case 1) The contracts deployed in Baobab at block number #75373310 recognizes 0x09, 0x0a, and 0x0b as addresses of vmLog, feePayer, and validateSender, respectively, and blake2f cannot be used.

case 2) The contracts deployed in Baobab at block number #75373314 recognizes 0x09 as the address of blake2f, and recognizes 0x3fd, 0x3fe, and 0xff as addresses of vmLog, feePayer, and validateSender.

Precompiled contracts related hardfork changes can be found at the bottom of this page. Go to Hardfork Changes.

USAGE IN YUL:

In Yul, staticcall remains the name of the opcode used for a low-level view function.

It’s a function with 6 parameters which includes:

  1. Gas: You need gas for every transaction, in yul, you specify the gas amount to spend for the transaction.
  2. Address: The address that will make the call. In the example above, it was a token address, you need to add the address variable if it’s passed as a parameter or reference it if it’s stored in the code.
  3. Input and Input size: is the area of the data(abi encoded) to the external contract.
  4. Output and output size: is the location in memory where the return value is.


function getUint(address addr, bytes memory data) internal view returns (uint result) {
result = 0;

assembly {
let status := staticcall(16000, addr, add(data, 32), mload(data), 0, 0)

// Success!
if eq(status, 1) {
if eq(returndatasize(), 32) {
returndatacopy(0, 0, 32)
result := mload(0)
}
}
}
}


function getUint2(address addr, bytes memory data) internal view returns (uint result) {


assembly {
let status := staticcall(gasLeft(), addr, add(data, 32), mload(data), 0, 0)

// Success!
if iszero(status) {
revert(0,0)
}
}
}

Useful Resources:

Klaytn precompiled contracts

Further reading on EIP214.

Conclusion:

Staticcall provides safe data retrieval. Understanding these primitives and their nuances is crucial for Klaytn developers. By harnessing their power while adhering to best practices and considering security considerations, developers can craft resilient, flexible, and efficient smart contracts that drive innovation in the blockchain space.

--

--