EIP-3860 Initcode size limit and metering (Limit and meter initcode)

Final · EVM & execution · EIP status Final · created 2021-07-16

Limits the maximum size of the initialization code (initcode) used to create a contract to 49152 bytes and charges an additional 2 gas for each 32-byte chunk.

Why it is needed

When a new contract is created on Ethereum, the node software (client) scans the entire initialization code before execution to perform jumpdest-analysis. The amount of work grows in proportion to the initcode length, but today that cost is not charged in gas and the protocol sets no size limit. In particular, when a contract creates another contract with the CREATE or CREATE2 instruction, there is no cost attached to the initcode itself, so variants of malicious initcode can be produced in bulk at low cost. In the past there was in fact a vulnerability that exploited malicious initcode, which was fixed in geth 1.6.5 in 2017. The absence of a limit has also influenced the design of other EVM proposals, and has caused features to be delayed or cancelled. This EIP makes the cost proportional to initcode length to reduce future risk, and sets an explicit limit to simplify EVM implementations.

What changes

Today

After EIP-3860

Who is affected

In depth

Constants and cost formula

There are two new constants. INITCODE_WORD_COST is 2, and MAX_INITCODE_SIZE is 2 * MAX_CODE_SIZE. MAX_CODE_SIZE is 24576 as set by EIP-170, so the initcode limit is 49152 bytes. The cost function is initcode_cost(initcode) = INITCODE_WORD_COST * ceil(len(initcode) / 32), which comes to 0.0625 gas per byte. The EVM does not allow fractional gas, so the cost is approximated by charging per word (32 bytes).

Four rules

First, if the data of a creation transaction (the initcode) exceeds MAX_INITCODE_SIZE, the transaction itself is invalid, similar to how a transaction with insufficient intrinsic gas is invalid. Second, initcode_cost is added to the data cost formula of a creation transaction. This is included in the intrinsic cost, so a transaction that cannot pay it is also invalid. Third, if the initcode passed to CREATE or CREATE2 exceeds the limit, execution is aborted exceptionally, as with running out of gas. Fourth, an additional initcode_cost of gas is charged for CREATE and CREATE2, and this deduction happens before the contract address is computed and before the initcode is executed. It comes before, or at the same point as, the hashing cost of CREATE2.

Why 2 gas per word was chosen

The benchmark baseline is the KECCAK256 hashing performance of geth 1.10.9, targeting a 70 Mgas/s gas limit on a 4.0GHz x86_64 CPU. The measured cost per 32 bytes was 2.0 for geth, 2.9 for evmone/Baseline, 13.8 for evmone/Advanced, and 6.0 for geth's KECCAK256, and 2 gas was chosen based on the geth implementation. Computing per word works the same way as the CREATE2 hashing cost (hashcost) defined in EIP-1014, so the implementation can be shared. The constants, which were CREATE 0 and CREATE2 6 before activation, become CREATE 2 and CREATE2 6 + 2 after activation.

Why a size limit is set and its effect

With a limit, it becomes easier to estimate and construct the worst case, because one variable that must be explored is greatly reduced. This allows the per-byte cost to be set much more optimistically. Without a limit, a higher price would have to be charged to cover unknown risks. Since most initcode does not exceed the proposed limit, it was judged unnecessary to penalize contracts with an overly conservative cost. The limit of 2 * MAX_CODE_SIZE allows MAX_CODE_SIZE for the runtime code and another MAX_CODE_SIZE for the constructor code in the basic case. However, it can be a practical constraint when deploying multiple contracts in a single creation transaction. The initcode cost of a creation transaction (0.0625 gas per byte) is negligible compared with the data cost (4 or 16 gas per byte), but it was included in the specification for consistency and future compatibility.

Why violations are handled as exceptional aborts

A limit violation in CREATE and CREATE2 is handled as an exceptional abort of execution. This puts the violation check into the group of early out-of-gas checks. This group includes stack underflow, memory expansion, static call violation, initcode hashing cost, and the initcode cost added by this EIP. These checks are performed before lighter checks such as call depth and balance. This choice keeps the check order consistent and lowers implementation complexity, since out-of-gas checks can be done in any order.

Backward compatibility and security effects

Because this changes consensus rules, a network upgrade (hard fork) is required. Already deployed contracts are not affected, but a transaction containing initcode over the limit can still be included in a block, and simply ends in an exceptional abort. For clients, attacks targeting jumpdest-analysis become less of a problem, which improves robustness. At the 30M gas limit on London at the time of writing the source, it was possible to trigger analysis of about 1.3GB of initcode in total, and with this EIP applied, the cost of such an attack increases by about 80M gas. The status of this EIP has been finalized as Final.

Original EIP · ← EIPs