1 of 19

EIP-7907: Meter Contract Code Size And Increase Limit

Qi Zhou

May 28, 2025

2 of 19

Outline

  • Motivation

  • EIP-7907

  • Advance Topics

  • Summary

3 of 19

Motivation

  • Before EIP-170, there is not limit of contract size
    • Resources of calling contract including CPU, storage, memory is linear in terms of the contract size, especially reading the code from storage can be very expensive
    • However, Ethereum charges constant gas cost (2600), resulting a denial-of-service (DoS) attack vector
  • E.g., suppose we deploy contracts with size 256KB, given the block gas limit 30M, then an attacker can create a block to read 30M / 2600 * 256KB = 2.8 GB data!
    • while we need to reach the consensus of the block within 12s!

  • EIP-170 introduces the 24KB limit of a contract to avoid DoS attack
    • As a result, the maximum code read from storage is limited by 30M / 2600 * 24KB = 270 MB

4 of 19

Motivation

  • With rapid growth of dApp, such as DEX, lending, etc, the logic of the contract is becoming more complicated, resulting in larger contract size

  • A complicated contract can now easily exceed 24KB

  • Solution: Split a contract into multiple sub-24KB small contracts
    • Increase maintenance cost
    • Introduce more complexity with more security concerns
  • A long pain point for Ethereum developers

5 of 19

Splitting a Large Contract to Smaller Ones

contract LargeContract {

function func1() {

}

function func2() {

}

}

contract SubContract0 {

function func2() {

….

}

}

contract SubContract1 {

function func1() {

}

function func2() {

call ABC0.func2()

}

}

6 of 19

EIP-7907 Solution

  • Remove 24KB contract limit
    • Impose 256KB contract limit because of other constraints in the Ethereum protocol
  • Design constraints
    • Avoid DoS attack with proper gas metering
    • Compatible with existing deployed contract calls
      • the gas call for <= 24KB contract is the same as before
  • Basic idea: introducing a dynamic gas metering based on contract size
    • code size <= 24KB => gas cost is the same as pre EIP-7907
    • code size > 24KB => gas cost = original gas cost + (size - 24KB) * 2 // 32 // excess gas
  • EIP-7907 is chosen as the candidate for inclusion (CFI) for Fukasa upgrade

7 of 19

Avoid DoS Attack: Charge Gas Before Consuming Resources

// call a contract in EIP-170

evm.use_gas(2600) // if not enough gas, then the tx will be revert

account = evm.read_account(contract_addr)

code = evm.read_contract(account.codehash) // expensive operation, EIP-170 guarantees code.size <= 24KB

evm.call_contract(...)

8 of 19

Avoid DoS Attack: Incorrect EIP-7907 Implementation

// call a contract in EIP-170

evm.use_gas(2600)

account = evm.read_account(contract_addr)

code = evm.read_contract(account.codehash) // code.size <= 24KB

evm.call_contract(...)

// call a contract (EIP-7907 wrong impl)

evm.use_gas(2600)

account = evm.read_account(contract_addr)

code = evm.read_contract(account.codehash) // DoS attack if code.size >> 24KB

excess_gas = max(0, code.size-24KB) * 2 // 32

evm.use_gas(excess_gas)

evm.call_contract(...)

9 of 19

Avoid DoS Attack: Indexing Approach

// call a contract (EIP-7907 wrong impl)

evm.use_gas(2600)

account = evm.read_account(contract_addr)

code = evm.read_contract(account.codehash) // DoS attack if code.size >> 24KB

excess_gas = max(0, code.size-24KB) * 2 // 32

evm.use_gas(excess_gas)

evm.call_contract(...)

// call a contract (EIP-7907 right impl)

evm.use_gas(2600)

account = evm.read_account(contract_addr)

codesize = evm.read_codesize(account.codehash) // additional index of codehash=>size

excess_gas = max(0, codesize-24KB) * 2 // 32

evm.use_gas(excess_gas)

code = evm.read_contract(contract_addr) // can be >> 24KB

evm.call_contract(contract_addr, code)

10 of 19

Advanced Topics

  • Cold/Warm State for code

  • Alternative implementation to efficiently read code size

  • Extra cost on JUMPDEST analysis

  • Large contract impact to cache

11 of 19

Cold/Warm State

  • EIP-2929 introduces cold/warm state
    • Cache mechanism to reduce gas cost to access the storage second time
  • E.g., Calling a contract twice in a Tx
    • First call will take 2,600 gas
    • Second call will take 100 gas

// call the contract first time

evm.use_gas(2600)

account = evm.read_account(contract_addr)

code = evm.read_contract(account.codehash)

evm.call_contract(...)

// call the contract second time

evm.use_gas(100)

// read from memory/cache

account = evm.read_account(contract_addr)

code = evm.read_contract(account.codehash)

evm.call_contract(...)

12 of 19

Cold/Warm State

  • EIP-2929 introduces cold/warm state
    • Cache mechanism to reduce gas cost to access the storage second time
  • Apply to two objects
    • Account
    • Storage slots (USDT balance)
  • Missing cold/warm state for code

// call the contract first time

evm.use_gas(2600)

account = evm.read_account(contract_addr)

code = evm.read_contract(account.codehash)

evm.call_contract(...)

// call the contract second time

evm.use_gas(100)

// read from memory/cache

account = evm.read_account(contract_addr)

code = evm.read_contract(account.codehash)

evm.call_contract(...)

13 of 19

Added Cold/Warm State for Code in EIP-7907

14 of 19

Read Code Size Efficiently

  • Concerns using an index of codehash => codesize
    • Update db schema (backfilling for old contracts)
    • Extra db lookup

  • Alternative method 1: Reuse existing DB features
    • Most of DB already knows the value size before reading it
    • Need to expose the APIs to public

  • Alternative method 2:

15 of 19

Read Code Size Efficiently

  • Concerns using an index of codehash => codesize
    • Update db schema (backfilling for old contracts)
    • Extra db lookup

  • Alternative method 2: Large contract detection
    • When writing
      • If len(code) < 24K, write codehash => code as usual
      • If len(code) > 24K, write codehash => code[:24K] + len(code).to_bytes, and write reset of code codehash => code[24K:] in another db space (usually use a different prefix)
    • When reading the code, read codehash => code
      • If len(code) < 24K, return code
      • If len(code) > 24K, the contract is a large one, then read the rest of code and concatenate them to obtain the code

16 of 19

JUMPDEST Analysis

  • JUMPDEST analysis finds valid (or invalid) jump destinations of a contract
    • In Geth, it is done when the contract is first call
  • Benchmark is done by Charles Cooper
  • The results show the cost is linear

17 of 19

Large Contract Impact to Cache

  • Worst-case scenario: pure long jumps to evict cache
  • Early results on modern commodity PC show 1% difference between 24KB and 256KB contracts
    • Need more results on devices with smaller caches

18 of 19

References:

19 of 19

Acknowledgement

  • Charles Cooper, Jochem Brouwer, Ben Adams, lightclient, roman, Marius Van Der Wijden, Paweł Bylica, etc