Skip to main content

Repricing for trie-size-dependent opcodes

An Impact Study on EIP-1884

Ethereum_Foundation

Sep 19, 2019



ABSTRACT

Dedaub was commissioned by the Ethereum Foundation to perform an impact study of Ethereum Improvement Proposals (EIP) 1884 on existing contracts.

EIP-1884 is set to be implemented into the upcoming Ethereum ‘Istanbul’ hard fork. More precisely, it:

  • increases the cost of opcode SLOAD from 200 to 800 gas
  • increases the cost of BALANCE and EXTCODEHASH from 400 to 700 gas
  • adds a new opcode SELFBALANCE with cost 5.
  • Due to a fixed gas limit (2300) imposed by the .send(...) and .transfer(...) Solidity functions, fallback functions that use these opcodes may now start to fail due to an out-of-gas exception.

SUMMARY


EXPERIMENTAL FINDINGS AND STUDY


On Friday, August 16th, Martin Holst Swende of the Ethereum Foundation asked a question on the ETHSecurity channel on Telegram about how to go about finding smart contracts whose fallback function may fail due to EIP-1884. Since Dedaub already had gas consumption analysis built into its core static analyses, we reached out on the same day with a list of contracts (continuously updated) that may be affected.

Over the subsequent days, also with the input of Martin Holst Swende, the gas cost analysis computation was updated and improved, over several iterations. The analysis currently reveals over 800 contracts that are highly likely to fail if called with 2300 gas (whereas they would succeed prior to EIP-1884). A subsequent, more general, analysis was also developed. This would be the most comprehensive list of possibly affected smart contracts for this particular issue, but also contains many false positives. This more general “may” analysis reveals that 7000 currently deployed smart contracts may fail under some execution paths with 2300 gas.

In addition, since our analysis is fully automated, we have also performed experiments to see whether these issues can be simply avoided by repricing the LOG0, LOG1 ... opcodes. Note that these opcodes tend to occur quite often in fallback functions. By halving the Glog and Glogtopic gas costs (refer to the yellow paper), the number of flagged contracts is reduced by approximately half!

Although repricing opcodes can always break contracts, the EVM should be able to evolve too. Clearly, a decent number of contracts will be broken due to this change, so care must be taken to lessen the impact on the overall ecosystem. In this case, we recommend repricing the LOGx opcodes, which seem to be mispriced anyway. This way, there will be fewer contracts affected.

A more interesting, but perhaps equally serious side-effect of EIP-1884 and EIP-2200 combined is that it lowers the cost of performing an unbounded mass iteration attack, which is currently quite high. This attack is described in MadMax. In summary, this is an attack carried out by an unauthorized user, to increase the size of an array or data structure, which is iterated upon by any other user, rendering the functionality inaccessible by increasing gas cost beyond the block gas limit. The combined effect of EIP-1884 and EIP-2200 make this kind of attack around 7 times cheaper on average, rendering it much more feasible. This attack requires 2 SSTOREs per array element that is added by the attacker. This array is then iterated upon by the victim, requiring an additional SLOAD. For a list of contracts that may be susceptible to unbounded iteration, we have you covered. The list contains approximately 15k contracts.

Which contracts will be affected? What about the one I’m currently developing?

If your contract does not have fallbacks which may fail with 2300 gas) or is not susceptible to unbounded iteration, then you’re most probably fine. If it is, you may still be ok, but further investigation is necessary. If you would like to see whether the contract you are developing may be affected, deploy it to one of the Ethereum testnets and check your results at contract-library.com.

Below are sample contracts with a non-zero Ether balance that are affected by the repricing of SLOAD operations, so that their fallback is no longer runnable under the send/transfer gas allowance of 2300.

Kyber Network

function() public payable {
require(reserveType[msg.sender] != ReserveType.NONE);
EtherReceival(msg.sender, msg.value);
}

NEXXO crowdsale

modifier onlyICO() {
require(now >= icoStartDate && now < icoEndDate, "CrowdSale is not running");
_;
}

function () public payable onlyICO{
require(!stopped, "CrowdSale is stopping");
}

For NEXXO, it checks three slots, icoStartDate, icoEndDate and stopped, totalling 2400 with new gas rules.

Crowd Machine Compute Token crowdsale

modifier onlyIfRunning {
require(running);
_;
}

function () public onlyIfRunning payable {
require(isApproved(msg.sender));
LogEthReceived(msg.sender, msg.value);
}

Important reminder: The crowdsales above do not inherently break, it just means that callers need to add some more gas than 2300 to partake in the ICO contracts.

CappedVault

function () public payable {
require(total() + msg.value <= limit);
}

Unknown Harvester

This Harvester had a 5 ETH balance. The below code has been decompiled from bytecode.

require((msg.value >= stor___function_selector__));
emit 0xafd096c64445a293507447c2ecb78f03b4f5459ec28b8e9bfe113c35b75d624a
(address(msg.sender), msg.value, 0x447);

No source code was available. Note that this contract would work if LOGx gas cost is reduced.

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

Note that this contract would also not work if LOGx gas cost is reduced. According to the Dedaub analysis, the fallback function may fail due to anything between 2308 and 2438 gas.


HOW DOES THE DEDAUB STATIC ANALYSIS WORK?

Static program analysis is a technique that considers all of a program’s behaviors without having to execute the program. Static analysis is generally thought to be expensive, but over the years we have developed techniques to counter this. Firstly, we developed new techniques in the area of “declarative program analysis”, which simplifies analysis implementations. Secondly, we have applied our analyses at scale, which makes them worth the effort. Dedaub’s internal analysis framework decompiles all smart contracts on the main Ethereum network and most popular testnets to an IR representation, amenable to analysis. The decompilation framework is described in a 2019 research paper. Following this analysis, many “client analyses”, are applied. These analyses all benefit from a rich suite of analysis primitives, such as gas cost analysis (similar to worst-case execution analysis), memory contents analysis, etc. These are instantiated and customized in each client analysis. Finally, we encode all our analyses, decompilers, etc. in a declarative language, and automatically synthesize a fast C++ implementation using Soufflé.

For illustration, the FALLBACK_WILL_FAIL static analysis is encoded in the following simplified datalog spec, deployed on Dedaub:

% Restrict the edges that form the possible paths to those in fallback functions
FallbackFunctionBlockEdge(from, to) :-
GlobalBlockEdge(from, to),
InFunction(from, f), FallbackFunction(f),
InFunction(to, g), FallbackFunction(g).
% Analyze the fallback function paths with the
% conventional gas semantics, taking shortest paths
GasCostAnalysis = new CostAnalysis(
Block_Gas, FallbackFunctionBlockEdge, 2300, min
).
% Analyze the fallback function paths with the
% updated gas semantics, taking shortest paths
EIP1884GasCostAnalysis = new CostAnalysis(
EIP1884Block_Gas, FallbackFunctionBlockEdge, 2300, min
).
FallbackWillFailAnyway(n - 2300) :-
GasCostAnalysis(*, n), n > 2300.
% fallback will fail with n - m additional gas
EIP1884FallbackWillFail(n - m) :-
EIP1884GasCostAnalysis(block, n), n > 2300,
GasCostAnalysis(block, m),
!FallbackWillFailAnyway(*).

The analysis performs a gas cost computation over all possible paths in the fallback functions, using the gas cost semantics of both PRE and POST EIP-1884. In cases where there is a path that can be completed in the former semantics but not the latter, we flag the smart contract.


ABOUT DEDAUB

Dedaub offers significant security expertise combined with cutting-edge program analysis technology to secure some of the most prominent protocols in DeFi. The founders, as well as many of Dedaub's auditors, have a strong academic research background together with a real-world hacker mentality to secure code. Protocol blockchain developers hire us for our foundational analysis tools and deep expertise in program analysis, reverse engineering, DeFi exploits, cryptography and financial mathematics.