Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 45 additions & 17 deletions EIPS/eip-7907.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,76 @@
type: Standards Track
category: Core
created: 2025-03-14
requires: 170, 2929, 3860
requires: 170, 2929, 3860, 7702
---

## Abstract

This EIP substantially increases the hard contract code size limit from 24KB (24576 bytes) introduced in [EIP-170](./eip-170.md) to 256KB, and adds gas metering. It introduces a gas cost of 2 gas per (32 byte) word for contract code exceeding 24KB, allowing deployment of contracts of any size while preventing DoS attacks through appropriate gas metering. Lastly, it also commensurately increases initcode size limit from 48KB, introduced in [EIP-3860](./eip-3860.md), to 512KB.
This EIP substantially increases the contract code size limit from 24KB (24576 bytes) introduced in [EIP-170](./eip-170.md) to 48KB (49152 bytes), and adds gas metering. It introduces a gas cost of 4 gas per (32 byte) word for contract code exceeding 24KB, allowing deployment of contracts of any size while preventing DoS attacks through appropriate gas metering. Lastly, it also commensurately increases initcode size limit from 48KB, introduced in [EIP-3860](./eip-3860.md), to 96KB (98304 bytes).

## Motivation

EIP-170 introduced a 24KB contract code size limit to prevent potential DoS attacks, as large contract code requires O(n) resource cost in terms of disk reads, VM preprocessing, and Merkle proof sizes, all of which are not directly compensated by gas fees. However, this limit restricts legitimate use cases for large contracts.

This EIP proposes a gas-based solution that allows contracts of larger size while ensuring that users loading large contracts pay gas proportional to the additional resources they consume. This approach aligns with Ethereum's gas model philosophy of paying for the resources consumed. A new limit has been set at 256KB, so that raising the gas limit does not break assumptions in the p2p layer.
This EIP proposes a gas-based solution that allows contracts of larger size while ensuring that users loading large contracts pay gas proportional to the additional resources they consume. This approach aligns with Ethereum's gas model philosophy of paying for the resources consumed. A new limit has been set at 48KB, so that raising the gas limit does not break assumptions in the p2p layer.

Improving developer experience is the primary motivation for increasing the contract size limit. The current 24KB ceiling forces developers to split functionality across multiple contracts, introduce proxies or delegatecall-based indirection, and rely on architectural patterns like the Diamond Standard—even when those patterns aren't otherwise necessary. These workarounds can increase code complexity, deployment costs, and audit surface. By raising the limit, developers can keep more logic in a single contract, improving readability and lowering gas usage by avoiding unnecessary cross-contract calls. This also makes smart contract development more accessible to newer developers, who can move from idea to deployment without first learning advanced contract composition patterns.

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174).
### Definitions

1. Update the [EIP-170](./eip-170.md) contract code size limit of 24KB (`0x6000` bytes) to 256KB (`0x40000` bytes).
2. Change the gas schedule for opcodes which load code. Specifically, the opcodes `CALL`, `STATICCALL`, `DELEGATECALL`, `CALLCODE` and `EXTCODECOPY` are modified so that `largeContractCost = ceil32(excess_contract_size) * GAS_INIT_CODE_WORD_COST // 32` gas is added to the access cost if the code is cold, where `excess_contract_size = max(0, contract_size - 0x6000)`, and `GAS_INIT_CODE_WORD_COST = 2`. (Cf. initcode metering: [EELS](https://github.com/ethereum/execution-specs/blob/1a587803e3e698407d204888b02342393f8b4fe5/src/ethereum/cancun/vm/gas.py#L269)). This introduces a new warm state for contract code - warm if the code has been loaded, cold if not.
| Name | Value | Description |
| --- | --- | --- |
| COLD_SLOAD_COST | `2100` | The cost charged for cold loading storage as defined by [EIP-2929](./eip-2929.md). |
| WARM_STORAGE_READ_COST | `100` | The cost charged for loading warm storage as defined by [EIP-2929](./eip-2929.md). |
| `COLD_ACCOUNT_ACCESS_COST` | `2600` | The cost charged for loading a cold account as defined by [EIP-2929](./eip-2929.md). |
| `GAS_PER_CODE_WORD` | `4` | The cost charged per word of code loaded beyond the intial `24KB` amount. |

#### Helpers

```python
def ceil32(n):
if n % 32 == 0:
return n // 32
else:
return n // 32 + 32

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return n // 32 + 32
return n // 32 + 1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused, the pricing formula also has division by 32 in it. I think something is not right here 🤔

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EIP 3860 calculates cost as INITCODE_WORD_COST * ceil(len(initcode) / 32)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think clearest would be (n + 31) // 32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can define it as a "macro" ceil32 in the eip


def excess_code_size(n):
return max(0, contract_size - 0x6000)
```

### Behavior

1. Update the [EIP-170](./eip-170.md) contract code size limit of 24KB (`0x6000` bytes) to 48KB (`0xc000` bytes).
2. Introduces a new cold/warm state for contract code. Specifically, change the gas schedule of operations that load code, e.g. the opcodes `CALL`, `STATICCALL`, `DELEGATECALL`, `CALLCODE` and `EXTCODECOPY` are modified so that flat `COLD_SLOAD_COST=2100` and dynamic `EXCESS_CODE_COST= ceil32(excess_code_size(len(code))) * GAS_CODE_LOAD_WORD_COST // 32` gas are added to the access cost if the code is cold. When the code is an [EIP-7702](./eip-7702.md) delegation to another account, if target account code is cold add additional gas should be accounted. Warming of the contract code is subjected to the journaling and can be reverted similar to other state warming in [EIP-2930](./eip-2930.md).
3. The cost for `EXTCODESIZE` is updated to acknowlege the potential for two database reads: once for the account (making it warm) and second for code size if bytecode is marked as cold. Bytecode will not be marked as warm as only codesize is read. In addition to the current pricing scheme defined under [EIP-2929](./eip-2929.md), the instruction will also be subject to the `COLD_SLOAD_COST=2100` if code is cold.
4. Update the [EIP-3860](./eip-3860.md) contract initcode size limit of 48KB (`0xc000` bytes) to 96KB (`0x18000` bytes).
5. If a large contract is the entry point of a transaction, the cost calculated in (2) is charged before the execution and contract code is marked as warm. This fee is not calculated towards the initial gas fee. In case of out-of-gas halt, execution will stop and the balance will not be transferred.

| Contract | Gas changes (only opcodes that load code) | How? |
| ----------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| Cold account and code | Add `largeContractCost` to `COLD_ACCOUNT_ACCESS_COST=2600` | Contract not in access list nor accessed prior in the txn |
| Warm account, cold code | Add `largeContractCost` to `WARM_STORAGE_READ_COST=100` | Already accessed balance, storage, or included in access list ([EIP-2930](./eip-2930.md)) |
| Warm account and code | No change to existing gas schedule. `WARM_STORAGE_READ_COST=100` | Contract created with `CREATE`/`CREATE2`, or `CALL`, `STATICCALL`, `DELEGATECALL`, `CALLCODE` or `EXTCODECOPY` made on the contract, previously in the txn (opcodes that load contract code) |

`COLD_ACCOUNT_ACCESS_COST` and `WARM_STORAGE_READ_COST` are defined in [EIP-2929](./eip-2929.md#parameters).
| Cold account and code | Add `COLD_SLOAD_COST=2100`, `EXCESS_CODE_COST`, and `COLD_ACCOUNT_ACCESS_COST=2600` | Contract not in access list nor accessed prior in the txn |

@jsign jsign Jul 3, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've two questions regarding this new COLD_SLOAD_COST=2100:

  1. Is there a TL;DR on the rationale behind this?
  2. This change increases the "breaking surface" compared with the original proposal (pre-PR), no? (i.e., already existing code-access opcodes targeting <=24KiB would have a different gas cost).

@rakita rakita Jul 3, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. It is about reading codesize before reading the full code, clients (impl detail) need to do an additional db read for it.
  2. It does increase the original price for opcodes that cold load code. Those are CALL, STATICCALL, DELEGATECALL, CALLCODE, EXTCODECOPY and EXTCODESIZE. Warm loads stays the same

@jsign jsign Jul 3, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. It is about reading codesize before reading the full code, clients need to do an additional db read for it.

Asking a bit more since I want to understand better: why EL clients need to read the code size first before pulling the bytecode from the DB? In practice is this done by any EL? Or maybe there's a new "mental model" now of baking this cost because maybe in the future if we keep increasing the max size, then maybe EL clients might require the code size to decide on how to pull the bytecode?

  1. It does increase the original price for opcodes that load code. Those are CALL, STATICCALL, DELEGATECALL, CALLCODE, EXTCODECOPY and EXTCODESIZE.

Do you know if anybody is planning to do some impact analysis on this? Mostly thinking if there are many contracts "baking" gas assumptions about CALL-like opcodes in particular (maybe that isn't normal -- mostly asking since I don't have well calibrated if this is a fair concern)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Ddos risk. Mostly, this then maybe EL clients might require the code size to decide on how to pull the bytecode? We need to check the size to calculate how much gas we need to spend before loading big bytecode.

  2. This is long standing question, not just related to this EIP, but for any gas change we do, it is hard to analyze, and I didn't find any good analysis. But if we want to increase the block size or make changes to EVM, changes in gas are generally inevitable.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @jsign that adding COLD_SLOAD_COST = 2100 to existing opcodes (e.g., CALL) constitutes a breaking change. Additionally, I believe this cost overcharges contract size lookups: COLD_SLOAD_COST = 2100 reflects the cost of reading from the storage trie, which requires multiple database lookups, whereas a contract size lookup typically involves just a single database access.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I view COLD_ACCOUNT_ACCESS_COST = 2600 as composed of 2100 + 500, where 2100 covers the cost of accessing the account from the state trie, and 500 accounts for loading the contract code (i.e., a single DB lookup via the codehash => codebytes mapping).

For contracts larger than 24KB, the 2600 cost implicitly includes the code size lookup. One possible solution is to split large contracts into two parts:

  • The first 24KB, stored as codehash => codebytes[0:24KB] + codesize.to_bytes();
  • The remaining bytes, stored as codehash => codebytes[24KB:].

When reading a contract, we charge the initial 2600 and load the first 24KB. If the loaded data is ≤ 24KB, we know the contract size <= 24KB and no excess gas is needed. If it's > 24KB, the appended codesize indicates the total size, and we can then charge excess gas accordingly.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not relevant for this EIP but I am starting to wonder why cold account cost is 500 more than cold slot cost. The tries are both "secure" tries (all keys are 32 bytes -> max depth thus 64). The account RLP itself needs some decoding and a few extra bytes of loading, but charging 500 for that is a bit excessive 🤔 (in context of EIP 2929)

Relevant: @qizhou I get the idea but you have an identical key (codehash) pointing to different data 😃

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not relevant for this EIP but I am starting to wonder why cold account cost is 500 more than cold slot cost. The tries are both "secure" tries (all keys are 32 bytes -> max depth thus 64). The account RLP itself needs some decoding and a few extra bytes of loading, but charging 500 for that is a bit excessive 🤔 (in context of EIP 2929)

Relevant: @qizhou I get the idea but you have an identical key (codehash) pointing to different data 😃

You can differentiate mappings by prefixing the same key differently — a standard practice in key-value databases.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not relevant for this EIP but I am starting to wonder why cold account cost is 500 more than cold slot cost. The tries are both "secure" tries (all keys are 32 bytes -> max depth thus 64). The account RLP itself needs some decoding and a few extra bytes of loading, but charging 500 for that is a bit excessive 🤔 (in context of EIP 2929)

This likely explains why 500 is used to represent the cost of accessing codebytes via the codehash key.

| Warm account and cold code | Add `COLD_SLOAD_COST=2100`, `EXCESS_CODE_COST`, and `WARM_STORAGE_READ_COST=100` | Already accessed balance, storage, or included in access list ([EIP-2930](./eip-2930.md)) |
| Warm account and code | `WARM_STORAGE_READ_COST=100` | Already accessed account code |

3. Update the [EIP-3860](./eip-3860.md) contract initcode size limit of 48KB (`0xc000` bytes) to 512KB (`0x80000` bytes).
4. If a large contract is the entry point of a transaction, the cost calculated in (2) is charged before the execution and contract code is marked as warm. This fee is not calculated towards the initial gas fee. In case of out-of-gas halt, execution will stop and the balance will not be transferred.
`COLD_ACCOUNT_ACCESS_COST`, `COLD_SLOAD_COST`, and `WARM_STORAGE_READ_COST` are defined in [EIP-2929](./eip-2929.md#parameters).

## Rationale

The gas cost of 2 per word was chosen in-line with [EIP-3860](./eip-3860.md). This accounts for:
The gas cost of 4 per word was chosen in-line with the per word code defined by [EIP-2929](./eip-2929.md)'s `COLD_ACCOUNT_ACCESS_COST`. The value is derived from the current gas per word code of `ceil(2600 / (24676//32)) = 4` where `2600` is the current cold account load cost and `24676` is the maximum allow code size at that price. In general, this accounts for:

1. The additional disk I/O for retrieving larger contract code
2. The increased computational resources for preprocessing larger code for execution (a.k.a. "JUMPDEST analysis").
3. The growth in Merkle proof sizes for blocks containing very large contracts

This EIP introduces the gas cost as an additional cost for contracts exceeding 24KB. It could have been specified as a simpler `ceil32(contract_size) * 2 // 32`, without hardcoding the existing contract size limit. However, for the sake of being conservative and avoiding lowering the cost of loading existing contracts (which could be small, under the 24KB limit), the 24KB floor was added to the formula.
This EIP introduces the gas cost as an additional cost for contracts exceeding 24KB. It could have been specified as a simpler `ceil32(contract_size) * 4 // 32`, without hardcoding the existing contract size limit. However, for the sake of being conservative and avoiding lowering the cost of loading existing contracts (which could be small, under the 24KB limit), the 24KB floor was added to the formula.

The `EXTCODECOPY` opcode could theoretically be exempt from this, since clients could just load the parts of the bytecode which are actually requested. However, this might require a change at the protocol level, since the full code is required for the block witness. For this reason, `EXTCODECOPY` is included in the pricing scheme, and a carveout could be considered at a later date.

The new limit has been set at 256KB. This is significantly larger than the limit implied by the current block gas limit of 35mm (~170KB). The limit has been put in place so that increasing the gas limit won't have unexpected side effects at the db or p2p layer. For instance, in devp2p, the maximum packet size is 10MB (https://github.com/ethereum/devp2p/blob/5713591d0366da78a913a811c7502d9ca91d29a8/caps/eth.md#basic-operation). As of time of this writing, the maximum packet size in snap sync is even lower, at 512KB.
The new limit has been set at `48KB`. The limit has been put in place so that increasing the gas limit won't have unexpected side effects at the db or p2p layer. For instance, in devp2p, the maximum packet size is 10MB (<https://github.com/ethereum/devp2p/blob/5713591d0366da78a913a811c7502d9ca91d29a8/caps/eth.md#basic-operation>). As of time of this writing, the maximum packet size in snap sync is even lower, at 96KB.

The limit for initcode has also been increased to 512KB, following the pattern set in EIP-3860 that the initcode limit is double the runtime code limit. While initcode is different from deployed code in that it does not live in the state and therefore isn't visible in devp2p or in the db, fully removing the limit could have unforeseen consequences.
The limit for initcode has also been increased to 96KB, following the pattern set in EIP-3860 that the initcode limit is double the runtime code limit. While initcode is different from deployed code in that it does not live in the state and therefore isn't visible in devp2p or in the db, fully removing the limit could have unforeseen consequences.

## Backwards Compatibility

Expand All @@ -74,3 +96,9 @@
## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).

<!-- markdownlint-configure-file

Check warning on line 100 in EIPS/eip-7907.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

HTML comments are only allowed while `status` is one of: `Draft`, `Withdrawn`

warning[markdown-html-comments]: HTML comments are only allowed while `status` is one of: `Draft`, `Withdrawn` --> EIPS/eip-7907.md | 100 | <!-- markdownlint-configure-file | = help: see https://ethereum.github.io/eipw/markdown-html-comments/
{
"line-length": false
}
-->
Loading