A unified multidimensional fee market is introduced, where each transaction specifies the maximum amount of ETH it is willing to pay for inclusion using a single max_fee
. Upon inclusion, the protocol ensures that the transaction is able to pay the gas for all dimensions, treating the max_fee
as fungible across resources. This enables a more efficient use of capital, and enshrines the same representation that users have when they interact with Ethereum. The fee market is further unified in terms of a single update fraction under a single fee update mechanism, generalized reserve pricing, and a gas normalization that retains current percentage ranges while keeping the price stable whenever a gas limit changes. Calldata is proposed as the first resource to be added, with avenues for facilitating gas fungibility for EVM resources considered for further expansion.
A multidimensional fee market enables precise control over resource consumption. It allows the market to fairly price resources according to targets and limits deemed safe by developers, and it allows resources to be consumed at maximum capacity within these limits. Directly expanding the current fee market design to the multidimensional setting can however have negative effects on the user experience (UX) and on economic efficiency. Users are forced to set a max_fee_per_gas
for each resource, where a too low allocation in any dimension can render the transaction ineligible.
This EIP leverages the natural fungibility of the user's fee budget by letting users set a single unified max_fee
. Instead of using non-fungible per-resource budgets, the single ETH budget can then be allocated dynamically to cover costs wherever they arise, ensuring a more efficient use of capital. Users will be able to specify a lower max_fee
than the implied aggregate maximum unless all base fees are perfectly correlated (which reduces to the current design), because they do not need to buffer for spurious price movements in a single resource dimension.
Ethereum's current fee market has "tech debt" in that two separate mechanisms are used: one for regular gas (EIP-1559) and the other for blob gas (EIP-4844). The proposal unifies the fee market under the preferred EIP-4844 design. That design allows for exact control over long-run resource consumption. In a multidimensional setting with individual base fees, we can then for example achieve precise control over state growth, while accommodating temporary spikes. Excess gas of EIP-4844 is further normalized relative to the limit, allowing for a single update fraction across resources that retains current percentage ranges while keeping the price stable if any gas limit changes.
Calldata is added first, to speed up worst-case payload propagation and expand available EVM gas—without compromising gas introspection. A method for facilitating gas aggregation across resources within the EVM is outlined as an avenue for preserving backward compatibility when expanding further. The logic of EIP-7918 is integrated into the multidimensional setting to ensure that calldata has a higher cost per byte than blob data.
The specification inherits its logic from EIP-7706, incorporating the changes necessary for facilitating one aggregate fee, a multidimensional EIP-7918 logic, a systematic approach to EIP-7805, and a stable gas normalization function, etc.
Constant | Value | Description |
---|---|---|
MULTIDIM_TX_TYPE |
TBD |
Identifier for the new transaction type |
EVM_LIMIT_TARGET_RATIO |
2 |
Ratio of EVM gas target to EVM gas limit |
CALLDATA_GAS_PER_TOKEN |
4 |
Gas cost per token for calldata |
TOKENS_PER_NONZERO_BYTE |
4 |
Tokens per non-zero byte of calldata |
CALLDATA_GAS_LIMIT_RATIO |
4 |
Ratio of calldata limit to gas limit |
CALLDATA_LIMIT_TARGET_RATIO |
4 |
Ratio of calldata target to calldata limit |
GAS_RESERVE_FACTOR |
[0, 16, 12] |
Factor by which the base fee can be below the baseline |
GAS_RESERVE_INDEX |
[0, 0, 1] |
Index of the base fee to compare against |
MIN_BASE_FEE_PER_GAS |
1 |
Minimum base fee per gas unit |
GAS_NORMALIZATION_FACTOR |
10**9 |
Normalization factor for the delta excess gas |
BASE_FEE_UPDATE_FRACTION |
4_245_093_508 |
≈ GAS_NORMALIZATION_FACTOR / (2 * ln(1.125)) |
Upon activation of this EIP via hard fork, a new EIP-2718 transaction is introduced with TransactionType
= MULTIDIM_TX_TYPE
.
The EIP-2718 TransactionPayload
for this transaction is
[chain_id, nonce, gas_limit, to, value, data, access_list, blob_versioned_hashes, max_fee, max_priority_fee_per_gas, y_parity, r, s]
We require max_fee
to be a scalar integer from 0
to 2**128-1
and max_priority_fee_per_gas
to be a list of integers from 0
to 2**64-1
, either of length 1 or with the same length as the number of resources (initially 3). The gas_limit
is initially specified only for the main EVM gas, since other limits can be inferred from the transaction. To facilitate further expansion, the Python spec uses a list with a single element at index 0. Each added integer will range 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 non-zero byte, 4 per zero byte) is removed.
Helpers for vector operations:
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_mul(v1: [int, int, int], v2: [int, int, int]) -> [int, int, int]:
return [x*y for x, y in zip(v1, v2)]
Helper functions for computing scalar max fees/priority fees for all transaction types:
def get_max_fee(tx: Transaction) -> int:
if tx.type == MULTIDIM_TX_TYPE: # New tx type already has a max_fee
return tx.max_fee
elif tx.type == BLOB_TX_TYPE: # Account for blobs in blob txs
blob_gas = len(tx.blob_versioned_hashes) * GAS_PER_BLOB
return tx.max_fee_per_gas*tx.gas_limit + tx.max_fee_per_blob_gas*blob_gas
elif is_eip_1559(tx.type): # EIP-1559 txs have no blobs
return tx.max_fee_per_gas * tx.gas_limit
else: # Legacy transactions have a gasprice
return tx.gasprice * tx.gas_limit
def get_priority_fee(tx, gas: list[int], base_fees: list[int], remaining_fee: int) -> int:
# Precalculate total gas and fees for non-blob resources
tip_indices = [0, 2]
tgas = sum(gas[i] for i in tip_indices)
tfee = sum(gas[i] * base_fees[i] for i in tip_indices)
if tx.type == MULTIDIM_TX_TYPE: # New tx type: Single tip or full vector of tips
if len(tx.max_priority_fee_per_gas) == 1:
tmax_fee = tx.max_fee - gas[1] * base_fees[1] # Ignore blobs (as now)
tpriority_fee = tmax_fee - tfee if tmax_fee > tfee else 0
max_priority_fee = min(tx.max_priority_fee_per_gas[0] * tgas, tpriority_fee)
else:
max_priority_fee = sum(vector_mul(tx.max_priority_fee_per_gas, gas))
return min(max_priority_fee, remaining_fee)
if is_legacy(tx.type): # Legacy tx: The remainder after base fees paid
tmax_fee = tx.gasprice * tgas
return tmax_fee - tfee if tmax_fee > tfee else 0
# EIP-1559 or Blob tx
tmax_fee = tx.max_fee_per_gas * tgas
tpriority_fee = tmax_fee - tfee if tmax_fee > tfee else 0
max_priority_fee = min(tx.max_priority_fee_per_gas * tgas, tpriority_fee)
if tx.type == BLOB_TX_TYPE: # Clamp since blobs reduce the shared max_fee budget
return min(max_priority_fee, remaining_fee)
return max_priority_fee
The calldata resource pricing under this EIP follows previous gas per byte constants but deprecates the floor pricing specified in 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
The helper for calculating gas limits from EIP-7706 is adjusted to subtract calldata gas from the gas limit of old transactions:
def get_gas_limits(tx: Transaction) -> list[int]:
calldata_gas = get_calldata_gas(tx.data)
blob_gas = len(getattr(tx, 'blob_versioned_hashes', [])) * GAS_PER_BLOB
if tx.type == MULTIDIM_TX_TYPE: # We use tx.gas_limit as is for new tx type
return [tx.gas_limit, blob_gas, calldata_gas]
else: # Partition old tx.gas_limit into its execution and calldata components.
require(tx.gas_limit >= calldata_gas)
execution_gas = tx.gas_limit - calldata_gas
return [execution_gas, blob_gas, calldata_gas]
The calculation for the required max_fee
to process the transaction is done in a separate function for easy future expansion:
def get_required_max_fee(base_fees: list[int], tx_gas_limits: list[int]) -> int:
return sum(vector_mul(base_fees, tx_gas_limits))
At the start of block processing:
gas_used_so_far
to [0, 0, 0]
.At the start of processing a transaction:
max_fee = get_max_fee(tx)
, base_fees = get_block_base_fees(block.parent)
,tx_gas_limits = get_gas_limits(tx)
.max_base = get_required_max_fee(base_fees, tx_gas_limits)
all_less_or_equal(vector_add(gas_used_so_far, tx_gas_limits), gas_limits)
. The block's gas_limits
are defined in the next subsection.max_base <= max_fee
max_priority_fee = get_priority_fee(tx, tx_gas_limits, base_fees, max_fee - max_base)
fee_to_deduct = max_base + max_priority_fee
fee_to_deduct
wei from the sender, which we define as the address recovered from the transaction’s signature. At the end of processing a transaction:
tx_gas_consumed
as a three-item vector, where the first item is the amount of execution gas actually consumed, and the second and third items are the blob and calldata gas amounts, which are equal to their limits from tx_gas_limits
.base_fee_paid = sum(vector_mul(base_fees, tx_gas_consumed))
.priority_fee_paid = get_priority_fee(tx, tx_gas_consumed, base_fees, fee_to_deduct - base_fee_paid)
fee_to_deduct - base_fee_paid - priority_fee_paid
.gas_used_so_far = vector_add(gas_used_so_far, tx_gas_consumed)
.At the end of processing a block:
block.gas_used
(a vector field in the header) to equal the corresponding element in gas_used_so_far
.The BlockHeader
is updated to remove the blob_gas_used
, gas_used
, base_fee_per_gas
, gas_limit
and excess_blob_gas
fields, and the following new fields are added, all of the [int, int, int]
type: gas_limits
, gas_used
, excess_gas
. The header sequence of the new fields is [..., withdrawals_root, gas_limits, gas_used, excess_gas]
.
We define the gas_limits
gas_limits[0]
follows the existing adjustment formula based on the parent gas_limits[0]
.gas_limits[1]
must equal blobSchedule.max * GAS_PER_BLOB
gas_limits[2]
must equal gas_limits[0] // CALLDATA_GAS_LIMIT_RATIO
.and the gas_targets
gas_targets[0]
must equal gas_limits[0] // EVM_LIMIT_TARGET_RATIO
.gas_targets[1]
must equal blobSchedule.target * GAS_PER_BLOB
.gas_targets[2]
must equal gas_limits[2] // CALLDATA_LIMIT_TARGET_RATIO
.The blobSchedule for referencing target and max blobs was introduced in EIP-7840.
We incorporate EIP-7918 to establish a dynamic reserve price for blob and calldata gas. When the market price for a resource drops below its reserve price, the mechanism for reducing its excess_gas
(and thus lowering its base fee) is disabled. This leads the base fee to rise with usage until it meets the reserve price.
We normalize the running excess gas so that all resources operate at the same scale with the same BASE_FEE_UPDATE_FRACTION
, which also provides a smoother response when any limit is adjusted. Division by gas_limits[i]
upholds the same price changes stipulated in EIP-4844 and EIP-7691.
def calc_excess_gas(parent: Header) -> list[int]:
base_fees = get_block_base_fees(parent)
limits = parent.gas_limits
targets = get_block_gas_targets(parent)
new_excess = []
for i in range(len(parent.excess_gas)):
if (GAS_RESERVE_FACTOR[i] > 0
and base_fees[i] * GAS_RESERVE_FACTOR[i] < base_fees[GAS_RESERVE_INDEX[i]]):
# EIP-7918 path. Excess gas rises with usage (up to limit), but cannot fall.
delta = parent.gas_used[i] * (limits[i] - targets[i]) // limits[i]
excess = parent.excess_gas[i] + delta * GAS_NORMALIZATION_FACTOR // limits[i]
else: # Regular path. Excess gas rises and falls with usage as normal
if parent.gas_used[i] >= targets[i]: # Add
delta = parent.gas_used[i] - targets[i]
excess = parent.excess_gas[i] + delta * GAS_NORMALIZATION_FACTOR // limits[i]
else: # ..or subtract
delta = targets[i] - parent.gas_used[i]
deltan = delta * GAS_NORMALIZATION_FACTOR // limits[i]
excess = 0 if parent.excess_gas[i] < deltan else parent.excess_gas[i] - deltan
new_excess.append(excess)
return new_excess
def get_block_base_fees(parent: Header) -> list[int]:
return [
fake_exponential(
MIN_BASE_FEE_PER_GAS,
excess,
BASE_FEE_UPDATE_FRACTION
)
for excess in parent.excess_gas
]
We add a CALLDATABASEFEE (0x4b)
instruction that returns the calldata base fee of the current block, following the same principles as specified in EIP-7516 for the blob base fee.
Op | Input | Output | Cost |
---|---|---|---|
0x4b | 0 | 1 | 2 |
In EIP-7805, includers propose inclusion-list (IL) transactions that the block builder must include, subject to rules evaluated post-execution once remaining capacity is known. We define three resource types based on these rules:
gas_limit[i] - gas_used[i]
) for at least one of its required conditional resources was insufficient to fit the transaction.If EIP-7805 is implemented, it must: (i) continue to treat EVM gas as a conditional resource; (ii) continue to treat blob gas as a deconditional resource; (iii) treat calldata gas as an unconditional resource. Any transaction listed in an IL that consumes calldata and is excluded from the block, despite there being sufficient capacity in all conditional resources it uses, MUST produce an INVALID_INCLUSION_LIST
.
Many Ethereum resources such as blobs, calldata, access, and compute are in limited supply each block, constrained by the need to, e.g., timely propagate data or run computations. Upholding the constraints on all these resources via a single meta-resource—"gas"—limits developers' ability to control both supply and demand. By going multidimensional, developers gain a more fine-grained control over the supply of each resource, preventing one from encroaching on the allotment for another. Resources can then be consumed at maximum capacity within each specified target and limit. With separate base fees for each resource, the market can come to an agreement on the appropriate price that leads to consumption at maximum capacity, given a specific user demand and aforementioned constraints on its supply. A multidimensional fee market is thus inherently a tool for scaling Ethereum.
Ethereum currently uses max_fee_per_gas
for EVM gas and max_fee_per_blob_gas
for blob gas. A direct expansion of this approach is proposed in EIP-7706, with a vector of fees per gas for each resource. This may be considered unfortunate from a UX perspective, given that the vector will expand with expanding dimensionality. Our casual users tend to be moderately confused already by a single max_fee_per_gas
. Most do not primarily think in terms of the individual prices for the resources that the transaction will consume. They think in terms of how much ETH they need to pay for their transactions (or in terms of dollars/fiat). A unified max_fee
is in this context an improvement to UX. A unified max_fee_per_gas
is possible, but might cause confusion in that the required gas price will differ from the base fees when using several resources—and thus also differ between transactions using different proportions of the resources.
When it comes to the priority fee, the optimal UX between using max_priority_fee
and max_priority_fee_per_gas
is a bit more nuanced. We decided to use max_priority_fee_per_gas
. Ethereum already today unifies the priority fee for blob gas and regular gas under a single max_priority_fee_per_gas
. It is thus natural to retain this representation, if only for removing friction for wallets. Furthermore, the priority fee should be determined from actual gas usage and not the gas limit. This is however perfectly possible when using max_priority_fee
as well, because the max priority fee can be treated as proportional to the limit during processing, thus falling if less gas is consumed.
Retaining max_priority_fee_per_gas
can be considered slightly safer for users, in that they will never risk manually submitting a max_priority_fee
for an old transaction type. Advanced users may wish to specify one priority fee per resource, to granularly pay according to usage when consuming below the gas limit. This ability was therefore preserved as an optional vector-based priority fee.
Besides UX, resource-specific fee limits can also be unfortunate at a deeper economic level. Once a user has specified a gas_limit
for any non-deterministic dimension, potentially with the assistance of their wallets, multiple separate max_fee_per_gas
could exclude transactions that specify a sufficient aggregate fee, due to a drift in relative levels of the base fees.
Consider a multidimensional transaction with a gas_limit
vector $\mathbf{l} = (l_1, l_2,\dots, l_n)$, a max_fee_per_gas
vector $\mathbf{f} = (f_1, f_2, \dots, f_n)$, and a max_priority_fee_per_gas
vector $\mathbf{p} = (p_1, p_2, \dots, p_n)$. The consumed gas of the transaction is denoted $\mathbf{g} = (g_1, g_2, \dots, g_n)$, and the vector of base fees is denoted $\mathbf{b} = (b_1, b_2, \dots, b_n)$. The realized priority fee, after ensuring a sufficient base fee, is then
$$ p{\prime}_i = \min(p_i, f_i-b_i) $$
for all resources $i$. Assume that when the transaction is submitted, the user specifies a max_fee_per_gas
vector $\mathbf{f}$ such that all entries individually satisfy all base fee $\mathbf{b}$ criteria
$$ f_i \ge b_i \quad \text{for all resources } i. $$
The gas limits also satisfy the actual gas consumption
$$ l_i \ge g_i \quad \text{for all resources } i. $$
The max_priority_fee_per_gas
vector $\mathbf{p}$ is also considered sufficient by many proposers, when they jointly weigh the reward against competing transactions using a weight vector $\mathbf{w}$, considering contention across relevant resources:
$$ \sum p{\prime}_i g_i \ge \sum w_i g_i. $$
While not evaluated in the existing EIP, the base fees $\mathbf{b}$ could also be satisfied in aggregate against the max fees:
$$ \sum f_i l_i \ge \sum b_i l_i. $$
Now assume that the base fee for any of the resources rapidly rises before the transaction is included, such that it becomes higher than the max_fee_per_gas
in that dimension. In this scenario, the transaction can no longer be included. This may happen, even though the aggregate fees that the user is offering to pay, $\sum f_i l_i$, remain at a level above the aggregate fees that the protocol demands to execute it, $\sum b_i l_i$, just as initially. The aggregate priority fees may still also satisfy the proposer. The welfare loss consists of a user, a proposer, and a protocol willing to process a transaction, hamstrung by rigidity in the protocol design.
The analysis leads to the following conclusions, all of which apply with increasing emphasis the higher the dimensionality:
Thus, the proposal is that the user specifies a single ETH max_fee
. The protocol first ensures that the max_fee
$F$ covers the base fee across gas limits in all dimensions, $F > \sum b_i l_i$, and finally charges the minimum aggregate fee possible. The total fee paid to the protocol is thus the same as in the original multidimensional fee market design. The design is future-proof in that additional dimensions easily are incorporated, retaining the single max_fee
.
Considerations for the priority fee were already outlined in the UX section. Here, we first expand on why a vector max_priority_fee_per_gas
can be beneficial to some users for fine-grained control, but not to most. The user has full control over the priority fee with a single input, as long as gas usage is deterministic across all resources. This input can be either max_priority_fee
or max_priority_fee_per_gas
—they are equivalent under deterministic gas usage. They are furthermore equivalent under any circumstances if the protocol makes sure to scale the max_priority_fee
according to realized gas usage relative to the specified limit. Otherwise, the max_priority_fee
will not be reduced when a transaction uses less gas than the limit (this is more often a drawback than a benefit for the user).
The priority fee a user wishes to provide per gas for a resource depends on the (likelihood of) contention across this resource. We can thus expect it to differ between resources. It follows from the principle of degrees of freedom that to gain full control over the transaction's priority fee, a user needs a separate parameter for each independent variable. When gas usage is non-deterministic in one resource—as today—the user needs two priority fees to achieve full granularity. When gas usage is non-deterministic in two resources (and their non-deterministic usage is not perfectly correlated), the user needs three priority fees, etc.
It should here be noted that the required priority fee per gas for some resource cannot be directly ascertained from the priority fee per gas that has been stipulated for that resource in recent transactions. Assume that there are 10 resource and each transaction includes a vector stipulating max_priority_fee_per_gas
for each. The builder will in its inclusion decision operate on the aggregate, and its requirements across dimensions can thus only be inferred, not observed directly. This means that per-resource estimates of required priority fees are not trivial, and most may still prefer the simplicity of a single one.
Ethereum currently uses two separate fee mechanisms, one for execution gas (EIP-1559) and one for blob gas (EIP-4844). This EIP unifies these mechanisms under the EIP-4844 standard, similar to EIP-7706 but with a few additions. We normalize the excess gas delta by dividing by the gas limit in calc_excess_gas
, thus enabling all resources to operate under the same BASE_FEE_UPDATE_FRACTION
while also keeping each fee fixed during a hard fork whenever a limit changes.
The reserve pricing mechanism from EIP-7918 is also generalized such that it can be applied to any resource, using another resource as an anchor. The mechanism imposes that if the base fee multiplied by GAS_RESERVE_FACTOR
is less than the anchor base fee, the base fee cannot fall any further. Calldata uses blob data as an anchor to ensure that we do not charge less for calldata than blob data (see the separate section on the calldata resource for further details).
To suggest a max_fee
, the wallet first simulates the transaction to obtain an estimate of the required limit(s) $\mathbf{l}$. It then queries the network for the current vector of base fees $\mathbf{b}$. The expected total fee is $\sum b_i l_i$. The wallet recommends a max_fee
that consists of this expected total cost plus a single buffer to account for aggregate volatility in base fees before the transaction is included. To suggest a max_priority_fee_per_gas
, the wallet analyzes recent blocks to determine the priority fees that ensure timely inclusion. It focuses on priority fees paid by recently included transactions consuming a similar distribution of resources, as well as overall network contention.
Historical data show that around 90% of blocks are below the gas limit. It is only when blocks are full that the builder is constrained in its block construction (disregarding timing games). Furthermore, a multidimensional knapsack problem only manifests in the event that a block is simultaneously full in multiple dimensions. Ignoring blobs, this is expected to be rare with the proposal, since calldata is assigned a limit four times above the target, which is already set higher than current consumption. The calldata limit is thus unlikely to be reached frequently. The blob dimension is a special case, in that this dimension already exists today, and the number of blob-carrying transactions is fairly low. Generally, the impact on revenue from sophisticated packing algorithms will likely be dwarfed by more significant MEV factors such as transaction ordering and private order flow.
Calldata is separated into its own resource. With the proposed constants, at 60M execution gas, each block will on average contain 60M/(CALLDATA_GAS_LIMIT_RATIO * CALLDATA_LIMIT_TARGET_RATIO) = 3.75M
gas of calldata. Focusing on non-zero bytes that cannot be Snappy-compressed, this gas corresponds to around 3.75M/16 = 234kB
. The average block size has been around 90kB
at a 36M gas limit, which would expand to 150kB
at a 60M gas limit if the proportion of calldata in the block remains the same. Using basic assumptions, this proposal thus increases the amount of calldata that will be consumed by over 50%, from 150kB
to 234kB
at 60M gas. As pointed out in EIP-7706, this will serve to decrease the cost of calldata for our users.
The limit is 60M/CALLDATA_GAS_LIMIT_RATIO = 15M
gas of calldata, corresponding to around 15M/16 = 938kB
. This is well below the limit under the current specification incorporating EIP‑7623, which is 60M/40 = 1.5MB
at 60M gas. Since calldata gas is also no longer accounted for as execution gas, the implied "aggregate" gas limit expands to 75M
gas, without materially affecting the maximum workload in terms of execution. In conclusion, the separation of calldata into its own resource increases calldata throughput while reducing costs. It furthermore facilitates scaling by allowing for faster payload propagation in the worst case and keeping all EVM gas available for other operations.
For censorship resistance (CR) purposes, under the currently proposed CR implementation EIP-7805, builders must include all transactions surfaced in any of the 16
inclusion lists (ILs) of each slot (each up to 8KiB
). However, if the block is full, builders can ignore the ILs, to not incentivize them to influence includers for MEV purposes.
The interaction between multiple separate resource limits and EIP-7805 is understudied and its implications have arguably been underestimated. We must be attentive to any resources that by their nature cannot be separated under EIP-7805 without sacrificing censorship resistance. These are the resources that require tight conditional limits. A special version of FOCILR (FOCIL with ranked transactions) that ranks transactions based on both priority fee and base fee is currently being developed for such resources. The good news is that calldata has properties allowing the builder to extract MEV while at the same time unconditionally adhering to its gas limit under EIP-7805. Specifically, in the case where all ILs are filled with disjoint transactions, the aggregate size of included transactions can still be at most 8KiB*16 = 131kB
. This leaves at the very minimum 938kB - 131kB = 807kB
of calldata for the builder to use as it sees fit when extracting MEV, which is sufficient according to the usage patterns we know.
Accordingly, calldata is treated as an "unconditional" resource, as defined in the specification. An includer MUST therefore signal INVALID_INCLUSION_LIST
if—and only if—a calldata‑using IL transaction is omitted despite sufficient capacity remaining in all conditional resources it uses.
An EIP-7918 reserve price is used for calldata just as for blobs. This ensures that the equilibrium price does not fall to levels where the fee market update mechanism stops working satisfactorily. Furthermore, the expiry window for blobs is much shorter than the planned rolling expiry window for calldata, making a modest relative reserve price motivated from a resource preservation perspective. Finally, without a reserve price on calldata, it could become cheaper than blob data (particularly below the blob reserve price), and thus a rational choice for L2s.
The latter rationale also motivates the specific parameterization chosen. The EIP-7918 reserve price per byte for calldata is set 1/3 above the price per byte for blob data. This is achieved by tying the EIP-7918 if clause to the blob base fee. The gas per byte is 16 for calldata and 1 for blob data, and the price floor is triggered when base_fees[1] > GAS_RESERVE_FACTOR[2] * base_fees[2]
. Given GAS_RESERVE_FACTOR[2] = 12
, the EIP-7918 condition activates when the blob base fee is more than 12
times higher than the calldata base fee, at which point calldata costs less than 16/12 = 1+1/3
of the blob price per byte. The calldata base fee is then imposed to not fall further.
We acknowledge that calldata already has been limited by EIP-7623, with further repricings proposed in EIP-7976. These EIPs limit worst-case block sizes by metering and conditionally pricing calldata at the transaction level as opposed to at the block level, having the calldata price vary with a transaction's execution usage. This may lead to secondary markets if users wish to combine different transactions to take advantage of the "rebate" on calldata when consumed together with EVM gas. Treating calldata as a separate resource with a price based on block usage would allow for a more precise control over usage, and transactors would not need to interact with a secondary market to achieve the best price.
Given that the EIP-7623 design already achieves calldata moderation, it must be remembered that an individual fee market for calldata would merely be a first step toward a multidimensional fee market. Calldata is not an endgame. It is likely best to transition over several hard forks, and the options available at the present must then be considered, which leads to a focus on calldata. Another present consideration is block level access lists (BALs) and their interaction.
A resource such as state is more attractive to separate than calldata. We could achieve exact control over state growth, while allowing temporary spikes many times above current gas limits. However, an expansion into other resources requires a multidimensional gas repricing, which has currently not yet been completed. The next section will further discuss remaining complexities of multidimensional EVM gas, and the strategies available for overcoming them.
This EIP has been designed to facilitate a future expansion into multiple EVM resources. We conclude by outlining the challenges inherent to such an expansion, and the different paths that it can take.
One long-term vision for the EVM is to move away from gas observability. This is one of the features of EOF (EIP-7692), e.g., through revamped CALL
instructions in EIP-7069 such as EXTCALL
. The new calls no longer accept a gas stipend as an input parameter, and the EVM instead makes available some reasonable fraction of all gas across dimensions (e.g., 63/64). Legitimate use cases previously handled via gas observability are then instead taken over by, e.g., the PAY
opcode EIP-5920.
For compatibility with legacy code, the EVM can in this scenario reinterpret legacy subcalls with a gas parameter by forwarding the same fraction of the caller’s remaining budget in each resource dimension. Concretely, if the call stipulates $g_c$ and the aggregate remaining EVM-gas budget is $g_a$, the callee receives, for each EVM resource with remaining budget $g_r$, the amount $\bigl\lfloor g_r \cdot \min!\bigl(1,\tfrac{g_c}{g_a}\bigr) \bigr\rfloor$. For completeness, the GAS
opcode could likewise return, e.g., $g_a$ (the per-call aggregate remaining budget at this point across all resource dimensions). Note, however, that reinterpreting legacy calls and GAS
in this way can still change the behavior of contracts that rely on precise gas observability or gas-capped subcalls, and such contracts may break.
It is possible to expand into multiple EVM resource dimensions without breaking existing contracts that rely on gas observability. By treating gas as fungible across resources (just as today), a single budget can be forwarded and counted toward any resource that consumes it. It should be noted that old contracts may still break due to repricing of the resources they use; indeed, a gas repricing effort is currently underway in Ethereum that likely will cause such breakage. But this may be treated as a separate concern.
A key difference between the new and old transaction types is that the old transaction types set only one limit, whereas the new can set several. When adding deterministic resources such as calldata, this is not a concern, because consumption of this resource can be deducted from the user-specified limit as a pre-processing step. But once there is more than one non-deterministic resource—as can be the case when EVM gas is separated into several resources—things get slightly more complicated. If we wish to ensure that old transaction types still can function properly under these circumstances, we must apply the single limit to multiple non-deterministic resources. The following subsections will outline how this can be achieved by aggregating the gas of these separate resources during EVM processing.
The new transaction type can supply several limits, making expansion into multiple EVM resources more straightforward. However, it turns out that we may also wish to retain the ability of the new transaction type to set a single limit for EVM gas. The reason is that the single limit and aggregate processing facilitates backward compatibility for contracts that rely on gas observability. As previously noted, these contracts must be able to supply subcalls with an aggregate gas stipend, to be counted against any EVM resource. Furthermore, there is an inherent simplicity of the single limit that is not to be discounted.
In all considered approaches below, the number of non-deterministic EVM resources still increases at the block level. To retain the ability to reject a transaction that may require more gas for a resource than its associated block limit, it becomes necessary to alter the check on gas_used_so_far
. Specifically, when a transaction stipulates only the main EVM gas limit, then this limit must be conservatively counted toward all underspecified dimensions of gas_used_so_far
before the limit check:
if len(tx_gas_limits) == len(gas_used_so_far) # Same check as previously
all_less_or_equal(vector_add(gas_used_so_far, tx_gas_limits), gas_limits)
else: # The joint EVM gas limit is expanded to cover all block resources
tx_gas_limits_exp = [tx_gas_limits[0]] * len(gas_limits)
tx_gas_limits_exp[1], tx_gas_limits_exp[2] = tx_gas_limits[1], tx_gas_limits[2]
all_less_or_equal(vector_add(gas_used_so_far, tx_gas_limits_exp), gas_limits)
An existing proposal by Inês Silva and Crapis is Multidimensional gas metering (not yet submitted as an EIP), which prices gas in one dimension, but applies limits at the block level across several dimensions when updating the (single) EVM base fee. The EVM will thus track multidimensional gas consumption, but perform the limit check on the aggregate, which is also passed on in subcalls. The proposed EIP could be combined with Multidimensional gas metering, for example by using a single EVM gas and calculating metered block gas usage separately, before submitting the outcome as a single resource dimension to the revamped calc_excess_gas()
.
To promote full resource utilization while maintaining backward compatibility for subcalls and old transaction formats, we could instead use separate gas prices for each EVM resource, while retaining within the EVM the ability to aggregate the gas consumption across resources. We refer to this as a Multidimensional fee market with aggregate gas. In the fully aggregated scenario, the user stipulates a single gas_limit
for EVM gas, and the protocol must make sure that the max_fee
covers the maximum possible fee, from consuming gas_limit
of the EVM resource that has the most expensive base fee. Execution is then fully backward compatible, and the EVM operates the same way as with Multidimensional gas metering.
The downside is that the user must stipulate an unnecessarily high max_fee
allocation, if the most expensive resource is not used. We present three options for alleviating this. The first two options transfer to the block producer the responsibility to ensure that the max_fee
indeed covers a transaction's fees determined post-execution. The third option instead enables the transactor to alternatively provide better guarantees in the form of full gas limits.
Option 1: Invalidate a block when the post-transaction check shows that the total fee for a transaction exceeded max_fee
. We could either completely remove the pre-execution check max_fee >= get_required_max_fee(base_fees, tx_gas_limits)
, or only check for deterministic resources in addition to EVM gas across the cheapest EVM resource. During processing, there would still be an aggregated check against the transaction's gas limit, which is the sender's responsibility. In extension, the block's gas limit is already safeguarded pre-execution by conservatively counting tx_gas_limits_exp
against it, as outlined in the previous subsection.
Note that the inclusion guarantees of EIP-7805 will not apply to the transactions where the max_fee
does not cover the worst-case pre-execution check. These transactions are to be ignored when validating the block post-execution.
Option 2: Give the block producer the ability and responsibility to supply any missing funds as part of the post-transaction check. This could potentially rely on staked builders currently envisioned in EIP-7732, albeit it would be more intuitive to use execution layer balances for this purpose. Another alternative is to charge a block-based base fee at the end of processing a block rather than per transaction, as has been discussed in the past. This naturally extends to a multidimensional setting. Note that (2) reverts to (1) upon failure of the builder to ensure the base fees are covered.
Option 3: Give the transactor the ability to provide limits for all dimensions, if they so wish. The pre-execution check can then be more fine-grained, inheriting the higher capital efficiency previously outlined. This is a hybrid design, where the transactor can choose the option most suitable to their needs if they use the new transaction type. This option will be discussed in more detail in the next subsection. Note that (3) can be combined with (1) if desirable.
In a Multidimensional fee market with hybrid EVM gas, the user has separate options:
It is further possible to give the user the freedom to stipulate some EVM gas limits, but not all. The user can for example set the gas limit to 0 for any resource it knows that it will not use, instead of not setting a gas limit for that resource. The aggregation then takes place only across resources without an individual stipulated limit, reducing the required max_fee
allocation while preserving full/a higher level of backward compatibility. These features come at a cost of somewhat increased complexity.
For the hybrid model, the get_gas_limits()
function must be updated to alternatively return multiple EVM gas limits, in the case the user stipulates the full list.
def get_gas_limits(tx: Transaction) -> list[int]:
...
if tx.type == MULTIDIM_TX_TYPE:
if len(tx.gas_limit) == 1:
return [tx.gas_limit[0], blob_gas, calldata_gas]
else:
return [tx.gas_limit[0], blob_gas, calldata_gas] + tx.gas_limit[1:]
...
For the aggregate model, the get_required_max_fee()
must be updated such that the protocol applies the worst-case EVM base fees across the EVM gas. The new constant EVM_INDICES
specifies the EVM resource indices. Note that TX_BASE_COST
is treated as a deterministic component of the EVM gas to reduce required capital allocation. Note that even when pursuing Options 1-2 for aggregate gas, the aggregation step is still applied in consideration of EIP-7805.
def get_required_max_fee(base_fees: list[int], tx_gas_limits: list[int]) -> int:
# Fully specified gas limits, apply baseline pattern
if len(base_fees) == len(tx_gas_limits):
return sum(vector_mul(base_fees, tx_gas_limits))
# Otherwise: 1. TX_BASE_COST is treated deterministically (assumed resource 0)
determ_evm_cost = TX_BASE_COST * base_fees[0]
require(tx_gas_limits[0] >= TX_BASE_COST) # Limit MUST cover TX_BASE_COST.
variable_evm_limit = tx_gas_limits[0] - TX_BASE_COST
# 2. Determine EVM cost based on most expensive resource
max_evm_base_fee = max(base_fees[i] for i in EVM_INDICES)
evm_cost = determ_evm_cost + variable_evm_limit * max_evm_base_fee
# 3. Return total EVM cost + blob cost + calldata cost
return evm_cost + base_fees[1]*tx_gas_limits[1] + base_fees[2]*tx_gas_limits[2]
For the hybrid model, the line stipulating tip_indices
in get_max_priority_fee()
must be updated to include all new EVM resources tip_indices = [2] + EVM_INDICES
. It is also feasible to adjust the functioning of legacy transactions, to let them tip only according to the headroom between the EVM resource with the highest base fee and the gasprice
. The aim would be to prevent them from having to pay an excessive tip when the gasprice
was set high merely to cover for the EVM resource with the highest base fee (of which they use little). The tip would then be calculated as the premium above the highest EVM resource base fee.
To allow transactors to specify some limits, the tx.gas_limit
list must instead have a nested format, where gas_limit[0]
is the aggregate limit for unspecified EVM resources, and [index, limit]
pairs then follow: tx.gas_limit = [aggregate, [[index, limit], [index, limit],...]]
. The protocol then computes the required max fee from resources with both specified and unspecified limits. The maximum base fee among the resources with unspecified limits is in this case multiplied with the aggregate, and this fee is summed together with the fees of specified limits.
The max_fee_per_gas
of the EIP-1559 and EIP-4844 transaction types are converted to a max_fee
by multiplication with the stipulated gas limit(s). The converted EIP-4844 transactions will no longer adhere to the individual max_fee_per_gas
and max_fee_per_blob_gas
, but instead to the aggregated max_fee
. This is a deliberate choice since previous individual checks have lower economic efficiency to no clear benefit, but it should nevertheless be noted by transactors. The priority fee retains the same functionality as previously. The old gasprice
can be used both for the max_fee
and max_priority_fee_per_gas
. The gas_limit
is finally derived deterministically. The section on expansion paths outlined how old transactions can retain functionality as we expand into several non-deterministic EVM resources.
Backward compatibility for contracts that rely on gas introspection can be resolved by giving users the ability to rely on aggregated EVM gas, while still potentially pricing EVM resources separately. Wallets must be updated to handle multidimensional gas accounting with several base fees.
One concern is the risk of increasing builder centralization due to increased revenue from sophisticated packing algorithms. While the builder is constrained in its block construction when blocks are full, it is only when several limits are reached at the same time that packing complexity markedly changes from today. Given that around 90% of blocks are below the gas limit, and the calldata limit is set to be very permissive, we argue that this will happen very rarely.
One reason for using max_priority_fee_per_gas
instead of max_priority_fee
is to establish the priority fee as always having a non-aggregate representation. We could possibly otherwise imagine a scenario where a user in the old transaction format manually submits an aggregate max_fee
and max_priority_fee
, causing loss of funds.
Copyright and related rights waived via CC0.