# Separate gas type for calldata

###### Video

###### Original

## Abstract

Add a new type of gas for transaction calldata. Add a new transaction type that provides `max_basefee`

and `priority_fee`

as a vector, providing values for execution gas, blob gas and calldata gas. Modify the basefee adjustment to use the same mechanism for the three types of gas.

## Motivation

A major argument against raising the Ethereum gas limit, making calldata cheaper, or increasing the EIP-4844 blob count before technologies like PeerDAS become available, is that the theoretical maximum size of an Ethereum block is already too large, and we cannot afford to increase it further. However, there is an inefficiency here: the current average size of a block (not including blobs) is ~100 kB, and the theoretical max is `30,000,000 / 16 = 1,875,000`

bytes (one could make larger blocks using zero bytes, but in practice zero-byte-heavy blocks would be compressed to less than 1.87 million bytes due to snappy compression). Ideally, we would have a way to bound the maximum, without making calldata more scarce *on average*.

This EIP does exactly this, by adopting the same technique that was applied for blob data in EIP-4844: we introduce a separate fee market for calldata, with a separate basefee and a separate per-block gas limit. The theoretical max calldata size of a block would be greatly reduced, while basic economic analysis suggests that *on average*, calldata would become considerably cheaper.

The EIP also introduces a new transaction type which includes the three types of max-basefees and priority fees as a vector, allowing the same code paths to handle all three types of gas. We also make the basefee adjustment, which currently uses separate mechanisms for execution gas (introduced in EIP-1559) and blobs (introduced in EIP-4844), use the same approach for all three types of gas. This simplifies the basefee adjustment rules, and ensures that the stronger mathematical properties of the newer EIP-4844 basefee adjustment algorithm cover all three types of gas.

## Specification

The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

### Parameters

`FORK_BLKNUM`

=`TBD`

`NEW_TX_TYPE`

=`TBD`

`CALLDATA_GAS_PER_TOKEN`

=`4`

`TOKENS_PER_NONZERO_BYTE`

=`4`

`CALLDATA_GAS_LIMIT_RATIO`

=`4`

`LIMIT_TARGET_RATIOS = [2, 2, 4]`

`MIN_BASE_FEE_PER_GAS = 1`

# Rename of EIP-4844 MIN_BASE_FEE_PER_BLOB_GAS`BASE_FEE_UPDATE_FRACTION = 8`

# Roughly matches EIP-4844 parameters

### New transaction type

As of `FORK_BLOCK_NUMBER`

, a new EIP-2718 transaction is introduced with `TransactionType`

= `TX_TYPE(NEW_TX_TYPE)`

.

The EIP-2718 `TransactionPayload`

for this transaction is

```
[chain_id, nonce, gas_limit, to, value, data, access_list, blob_versioned_hashes, max_fees_per_gas, priority_fees_per_gas, y_parity, r, s]
```

We require `max_fees_per_gas`

and `priority_fees_per_gas`

to be length-3 vectors, each of which contain integers from `0`

to `2**64-1`

.

The intrinsic cost of the new transaction is inherited from EIP-4844, except that the calldata gas cost (16 per nonzero byte, 4 per zero byte) is removed.

### Block processing and transaction fees

We add the functions `get_max_fees`

and `get_priority_fees`

, to compute these length-3 vectors for previous transaction types:

```
def get_max_fees(tx: Transaction) -> [int, int, int]:
if tx.type == NEW_TX_TYPE:
return tx.max_fees_per_gas
elif tx.type == BLOB_TX_TYPE:
return [tx.max_fee_per_gas, tx.max_fee_per_blob_gas, tx.max_fee_per_gas]
elif is_eip_1559(tx.type):
return [tx.max_fee_per_gas, 0, tx.max_fee_per_gas]
else:
return [tx.gasprice, 0, tx.gasprice]
```

```
def get_priority_fees(tx: Transaction) -> [int, int, int]:
if tx.type == NEW_TX_TYPE:
return tx.priority_fees_per_gas
elif tx.type == BLOB_TX_TYPE:
return [tx.max_priority_fee_per_gas, 0, tx.max_priority_fee_per_gas]
elif is_eip_1559(tx.type):
return [tx.max_priority_fee_per_gas, 0, tx.max_priority_fee_per_gas]
else:
return [tx.gasprice, 0, tx.gasprice]
```

We also add some helpers:

```
def all_less_or_equal(v1: [int, int, int], v2: [int, int, int]) -> bool:
return all(x <= y for x, y in zip(v1, v2))
def vector_add(v1: [int, int, int], v2: [int, int, int]) -> [int, int, int]:
return [x+y for x, y in zip(v1, v2)]
def vector_subtract(v1: [int, int, int], v2: [int, int, int]) -> [int, int, int]:
return [x-y for x, y in zip(v1, v2)]
def vector_subtract_clamp_at_zero(v1: [int, int, int], v2: [int, int, int]) -> [uint, uint, uint]:
return [x-y if x >= y else 0 for x, y in zip(v1, v2)]
def vector_mul(v1: [int, int, int], v2: [int, int, int]) -> [int, int, int]:
return [x*y for x, y in zip(v1, v2)]
```

```
# Same rules as current calldata pricing, but rephrased (similar language to EIP-7623)
def get_calldata_gas(calldata: bytes) -> int:
tokens = calldata.count(0) + (len(calldata) - calldata.count(0)) * TOKENS_PER_NONZERO_BYTE
return tokens * CALLDATA_GAS_PER_TOKEN
```

```
def get_gaslimits(tx: Transaction) -> [int, int, int]:
if tx.type == NEW_TX_TYPE:
return [tx.gaslimit, len(tx.blob_versioned_hashes) * GAS_PER_BLOB, get_calldata_gas(tx.data)]
elif tx.type == BLOB_TX_TYPE:
return [tx.gaslimit, len(tx.blob_versioned_hashes) * GAS_PER_BLOB, get_calldata_gas(tx.data)]
elif is_eip_1559(tx.type):
return [tx.gaslimit, 0, get_calldata_gas(tx.data)]
else:
return [tx.gaslimit, 0, get_calldata_gas(tx.data)]
```

```
def get_fees_per_gas(tx: Transaction, block_basefees: [int, int, int]) -> [int, int, int]:
max_fees = get_max_fees(tx)
priority_fees = get_priority_fees(tx)
output = []
# Fee sufficiency check, similar to EIP-1559 and 4844
require(all_less_or_equal(block_basefees, max_fees))
# Similar logic to EIP-1559 and 4844
return [
min(basefee + priority_fee, max_fee)
for block_basefee, max_fee, priority_fee in zip(block_basefees, max_fees, priority_fees)
]
```

```
get_block_gaslimits(block: Block) -> [int, int, int]:
return [block.gaslimit, MAX_BLOB_GAS_PER_BLOCK, block.gaslimit // CALLDATA_GAS_LIMIT_RATIO]
```

**At the start of block processing**:

- We initialize a vector
`gas_used_so_far`

to`[0, 0, 0]`

**At the start of processing a transaction**:

- Compute
`fees_per_gas = get_fees_per_gas(tx, get_block_basefees(block))`

and`tx_gaslimits = get_gaslimits(tx)`

- Check that
`all_less_or_equal(vector_add(gas_used_so_far, tx_gaslimits), block.gas_limits)`

- Deduct
`sum(vector_mul(fees_per_gas, tx_gaslimits))`

wei from the`tx.origin`

account

Note that `get_block_basefees(block)`

is not yet defined, we will define it in the section below. The `block.gas_limits`

field is also defined in the section below.

**At the end of processing a transaction**:

- Compute
`tx_gas_consumed`

as a three item vector, where the first item is the amount of gas actually consumed by the transaction execution, and the second and third match the values in`get_gaslimits(tx)`

- Refund
`sum(vector_mul(fees_per_gas, vector_sub(tx_gaslimits, tx_gas_consumed)))`

to the`tx.origin`

account (in practice, only the first term will be nonzero for now) - Set
`gas_used_so_far = vector_add(gas_used_so_far, tx_gas_consumed)`

**At the end of processing a block**:

- Require
`block.gas_used = gas_used_so_far`

The `block.gas_used`

field will be defined in the section below.

### Block structure:

We update `BlockHeader`

field, to remove the `blob_gas_used`

, `gas_used`

, `base_fee_per_gas`

, `gas_limit`

and `excess_blob_gas`

fields, and we add new fields, all of the `[int, int, int]`

type: `gas_limits`

, `gas_used`

, `excess_gas`

. The resulting RLP encoding becomes:

```
rlp([
parent_hash,
0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347, # ommers hash
coinbase,
state_root,
txs_root,
receipts_root,
logs_bloom,
0, # difficulty
number,
timestamp,
extradata,
prev_randao,
0x0000000000000000, # nonce
withdrawals_root,
gas_limits,
gas_used,
excess_gas
])
```

We define:

```
get_block_gas_targets(parent: Header) -> [int, int, int]:
return [limit // target_ratio for limit, target_ratio in zip(parent.gas_limits, LIMIT_TARGET_RATIOS)]
```

We calculate the required `excess_gas`

values as follows:

```
def calc_excess_gas(parent: Header) -> [int, int, int]:
return vector_subtract_clamp_at_zero(vector_add(parent_excess, parent_used), get_block_gas_targets(parent))
```

We calculate the required `gas_limits`

as follows:

`gas_limits[0]`

must follow the existing adjustment formula`gas_limits[1]`

must equal`MAX_BLOB_GAS_PER_BLOCK`

`gas_limits[2]`

must equal`gas_limits[0] // CALLDATA_GAS_LIMIT_RATIO`

Now, we define `get_block_basefees`

:

```
def get_block_basefees(parent: Header) -> [int, int, int]:
return [
fake_exponential(
MIN_BASE_FEE_PER_GAS,
excess_gas,
target * BASE_FEE_UPDATE_FRACTION
)
for (excess_gas, target) in zip(parent.excess_gas, get_block_gas_targets(parent))
]
```

## Rationale

### Conversion of all gas-related mechanics into vectors

This allows the same logic that is used for handling gas to handle all three types of gas. As a result, it's arguably a net simplification of protocol gas handling logic, despite the fact that the total number of gas types increases from 2 to 3

### Target ratios

The target ratios for execution gas and blobs are set to 2; the target ratio for calldata is set to 4. This greatly decreases the number of scenarios in which calldata actually hits the limit, which mitigates economic impact of the EIP, because analysis of EIP-1559-style fee markets is much simpler in "under-the-limit" conditions than in "at-the-limit" conditions. Additionally, it reduces the risk that applications requiring large calldata will outright stop working.

The current parameters set the target calldata per block to 187,500 bytes, about 2x the current average. Using basic supply-and-demand reasoning, this implies that calldata is likely to become significantly cheaper as a result of this EIP.

## Backwards Compatibility

Previous transaction types set the calldata basefee and priority fee to equal each other. The calldata gas costs were intentionally set to be identical to today, and the gas target similar to present-day usage, so that setting the two fees to be equal each other is a reasonable approximation to optimal behavior. In practice, the new transaction type would be superior, so we expect users to switch to it over time. However, the loss suffered by old-style transaction users would not be that high, because priority fees are generally small compared to basefees, and the amount that a user pays is proportional to the basefee.

## Security Considerations

Optimal block building behavior becomes more complex as a result of this EIP, particularly under the boundary conditions when blocks are full of one or both types of gas. We argue that the effects of this are not too large, because in practice over 90% of blocks are under-full, and naive "greedy algorithms" can get a close-enough-to-optimal outcome. The centralization risks of proprietary block-building algorithms are thus likely to be much smaller than other existing risks with eg. MEV extraction.

## Copyright

Copyright and related rights waived via CC0.

###### Adopted by projects

### Not miss a beat of EIPs' update?

Subscribe EIPs Fun to receive the latest updates of EIPs Good for Buidlers to follow up.

View all