Check out our new ERC20 Verification Audit!

Istanbul Hardfork EIPs – Changing Gas Costs and More

The Ethereum network will soon have its next hardfork called Istanbul. Many Ethereum Improvment Proposals (EIPs) were submitted to be included in that hardfork. Six EIPs have been accepted for Istanbul, but due to the large number of proposals eight have been tentatively accepted and will likely be part of another hardfork, called Berlin. The list of proposed EIPs can be found here.

In this article we will briefly touch on some of the EIPs of the Istanbul hardfork while going into more detail about EIP-1884, which ChainSecurity has been researching.

Let us start with an overview of the accepted EIPs and then dive deeper into EIP-1884.

EIP-152 will add a new Blake2b precompile to the EVM. This is a hashing function similar to keccak256 but said to be faster in general purpose solutions. It is also what is used by Zcash and therefore allows interoperability with Zcash on the Ethereum main network.

EIP-1344 will add a new CHAINID opcode which returns the id of the current chain. Recall that there are multiple Ethereum chains exist which differ in their id, e.g. mainnet has id 1 and ropsten has id 3. This will help prevent pre-signed data from being replayed on different chains. If a contract accepts pre-signed data which it validates by using ecrecover, it can now also include the chain id in the pre-signed data. By checking that this chain’s id matches the current chain’s id of the Ethereum node replay attacks on different chains are prevented.

EIP-1884 Due to the growth of the Ethereum state certain opcodes now are more resource-intensive than they used to be. Therefore, EIP-1884 raises the gas costs for the trie-size-dependent opcodes:

  • SLOAD: gas cost changes from 200 to 800 gas;
  • BALANCE: gas cost changes from 400 to 700 gas;
  • EXTCODEHASH: gas cost changes from 400 to 700 gas.

Furthermore, the EIP will introduce a new opcode:

  • SELFBALANCE: BALANCE of current contract, 5 gas

EIP-1108 reduces the gas cost of the alt_bn128 precompile contract. Client implementation have gotten more efficient implementations for this precompiled contract which allows the gas reduction and makes it cheaper to perform cryptographic operations on-chain.

EIP-2028 reduces the gas cost per non-zero byte of calldata from 68 to 16. Calldata is the input data that is passed to a transaction. Hence, transactions with a lot of input data, e.g. contract deployments and proof verifications, will become considerably cheaper.

Shortly before the previous Constantinople hardfork a bug was found in EIP-1283. EIP-1283 decreased the gas cost of writing to dirty storage slots within a single transaction. This made a reentrancy possible in certain cases. Due to this, EIP-1283 was retracted from the Constantinople hardfork. With the upcoming Istanbul hardfork an updated version of this EIP will be added, namely EIP-2200.

EIP-2200 is identical to EIP-1283, except for two changes. First, due to the previously described gas cost increase of SLOAD, the gas cost of updating a dirty storage slot has also been increased from 200 to 800. Secondly, to prevent the reentrancy bug a check is added that disallows an SSTORE to be executed if the gasleft is 2300 or less.

Comments

EIP-152
This EIP simply adds more functionality and flexibility. There are no known security problems with BLAKE2.

EIP-1344
With the new opcode CHAINID it now becomes possible to prevent replaying pre-signed data on multiple chains. It won’t take long before Solidity adds a global variable to execute this opcode, e.g. chainid(). If a contract verifies pre-signed data using ecrecover we would expect the chain id to be included in the pre-signed data, as well as a check that it matches the current chain id. This EIP seems to add value and is great for signature schemes as it provides additional context which is generally good for signatures.

EIP-2200
With SSTORE being cheaper for dirty slots, certain transactions will become cheaper. For new contracts the effect might be limited, as the solidity compiler has become better at optimizing multiple storage writes into a single storage write. As there is now a cheaper way to perform state changes developers need to stop using .gas(5000) to protect against state changes. Instead they should try to use STATICCALL, which provides EVM-level protection against state changes in the called contract.

EIP-1884
The Ethereum blockchain grows with time as it is recording all important information. As you can see below the chain data size of the parity client increased by roughly 50% since the beginning of the year.


More information in the database results in increasing lookup times and hence, increasing verification times. Below you see an analysis from Martin Holst Swende. The black line shows the average relation between gas costs and execution costs. As it is going up over time the SLOAD operation becomes “too cheap” relative to its execution cost, hence the EIP proposes to raise the cost for SLOAD and other opcodes.

 

Such mismatches between gas and execution costs open the door for denial-of-service attacks. Hence, EIP-1884 proposes to increase the gas cost. This sounds reasonable given the increased computational work needed to look entries up in the growing database. Nevertheless, increasing the gas cost for certain operations has certain side effects. Hubert Ritzdorf and other security engineers found multiple smart contracts which indirectly assume a fixed gas price. If for example a function can be executed with the current stipend of 2300 gas, it may not execute anymore because it exceeds the gas stipend. Therefore, EIP-1884 is not backward compatible. It needs to be mentioned, that this is not the first time that the gas price has been adjusted for SLOAD. The gas cost of SLOAD was raised from 50 to 200 in October 2016.

The following examples illustrate cases where contracts would break for two main causes: 1) Fallback function which consumes close to the current gas stipend and 2) Providing a fixed amount of gas to a function call. We do not intend to blame the developers as they, in most cases, followed best practices. For more examples, see here.

Fallback function which consumes close to the current gas stipend

Most commonly the contracts use a fallback function which consumes a little less than the current gas stipend. Some famous code examples are:

KyberNetwork

function() public payable {
        require(reserveType[msg.sender] != ReserveType.NONE);
        EtherReceival(msg.sender, msg.value);
}
  • This function is frequently called with 2300 gas as part of KyberSwap’s operation. Its execution contains one SLOAD due to the reserveType[msg.sender] lookup. Currently, the execution consumes 1943 gas. It will break with EIP-1884 as SLOAD then requires 600 gas more and hence the execution cost of 2543 gas exceed the gas limit of 2300.

Aragon’s DepositableDelegateProxy

    function isDepositable() public view returns (bool) {
        return DEPOSITABLE_POSITION.getStorageBool();
    }
    event ProxyDeposit(address sender, uint256 value);
    function () external payable {
        // send / transfer
        if (gasleft() < FWD_GAS_LIMIT) {
            require(msg.value > 0 && msg.data.length == 0);
            require(isDepositable());
            emit ProxyDeposit(msg.sender, msg.value);
        } else { // all calls except for send or transfer
            address target = implementation();
            delegatedFwd(target, msg.data);
        }
    }
  • This function checks how much gas was provided and if it was called with 2300 gas, it only consumes 1759 gas. its execution contains one SLOAD due to isDepositable(). Therefore, it will break with EIP-1884 as the new gas cost would be 2359 gas;
  • Aragon is aware of the issue and has fixed it, more information is available here

Providing a fixed amount of gas to a function call

Totle

 let success := call( 
                     5000, // Amount of gas 
                     token, // Address to call 
                     0, // ether to send
  • Totle combines multiple Decentralized Exchange Platforms (e.g. KyberSwap);
  • When the TotlePrimary contract executes, it internally calls getDecimals on the target token contract. As part of this call, getDecimals only passes 5000 gas as seen above. If the traded token is Augur’s REP token, the Delegator will be called. This call currently consumes 4511 gas and successfully returns 18. However, this call also contains three invocations of SLOAD so it will cost 6311 after the activation of the Istanbul hardfork. Hence, it will break after the hardfork;
  • As certain tokens do not implement the decimals function the failing call (due to out-of-gas) will be interpreted as 0 decimals. As the decimals are used to calculate token quantities, incorrect token quantities will be computed. Threfore, the security check for the minimum rate could be circumvented;
  • Totle is aware of the issue and has provided a fix.

Contract Library has also identified affected contracts through static analysis:
https://contract-library.com/?w=FALLBACK_WILL_FAIL

Further security considerations on EIP-1884 can be found at:
https://github.com/holiman/eip-1884-security

Resulting imbalances in gas costs

This analysis highlights the resulting inconsistencies of the EIP-1884 gas cost changes. As SLOAD becomes more expensive, it is cheaper to read from another contract through EXTCODECOPY rather than reading from the currently executed contract through SLOAD. This might have to addressed in future hardforks.

Conclusion & Take-Aways

In this hardfork gas prices will be adjusted to account for new realities. Certain transactions, e.g. on-chain STARK/SNARK verification, could become significantly cheaper. The higher gas costs for the increasing computational power, that is needed to look up entries in the database, breaks even popular contracts written according to best practices. This highlights the tension between protocol security and usability. From a usability perspective all changes should be backwards compatible but from a protocol security perspective certain changes will be necessary.

It has become clear that gas costs should not be assumed to be constant. This also has to be reflected in smart contract development. Hence, Consensys and others have changed their recommendations and now recommend to use .call.value() instead of .transfer() in Solidity as it passes all of the available gas. This is the opposite recommendation that was given just a short time ago. However, when using .call.value() separate protection against reentrancies, e.g. a ReentrancyGuard, is needed.