Cointime

Download App
iOS & Android

Multidimensional gas pricing

Special thanks to Ansgar Dietrichs, Barnabe Monnot and Davide Crapis for feedback and review.

In Ethereum, resources were up until recently limited, and priced, using a single resource called "gas". Gas is a measure of the amount of "computational effort" needed to process a given transaction or block. Gas merges together multiple types of "effort", most notably:

  • Raw computation (eg. ADDMULTIPLY)
  • Reading and writing to Ethereum's storage (eg. SSTORESLOAD, ETH transfers)
  • Data bandwidth
  • Cost of generating a ZK-SNARK proof of the block

For example, this transaction that I sent cost a total of 47,085 gas. This is split between (i) a "base cost" of 21000 gas, (ii) 1556 gas for the bytes in the calldata included as part of the transaction (iii) 16500 gas for reading and writing to storage, (iv) gas 2149 for making a log, and the rest for EVM execution. The transaction fee that a user must pay is proportional to the gas that the transaction consumes. A block can contain up to a maximum of 30 million gas, and gas prices are constantly adjusted via the EIP-1559 targeting mechanism, ensuring that on average, blocks contain 15 million gas.

This approach has one major efficiency: because everything is merged into one virtual resource, it leads to a very simple market design. Optimizing a transaction to minimize costs is easy, optimizing a block to collect the highest possible fees is relatively easy (not including MEV), and there are no weird incentives that encourage some transactions to bundle with other transactions to save on fees.

But this approach also has one major inefficiency: it treats different resources as being mutually convertible, when the actual underlying limits of what the network can handle are not. One way to understand this issue is to look at this diagram:

The gas limit enforces a constraint of 𝑥1∗𝑑𝑎𝑡𝑎+𝑥2∗𝑐𝑜𝑚𝑝𝑢𝑡𝑎𝑡𝑖𝑜𝑛<𝑁. The actual underlying safety constraint is often closer to 𝑚𝑎𝑥(𝑥1∗𝑑𝑎𝑡𝑎,𝑥2∗𝑐𝑜𝑚𝑝𝑢𝑡𝑎𝑡𝑖𝑜𝑛)<𝑁. This discrepancy leads to either the gas limit needlessly excluding actually-safe blocks, or accepting actually-unsafe blocks, or some mixture of both.

If there are 𝑛 resources that have distinct safety limits, then one-dimensional gas plausibly reduces throughput by up to a factor of 𝑛. For this reason, there has for a long time been interest in the concept of multi-dimensional gas, and with EIP-4844 we actually have multi-dimensional gas working on Ethereum today. This post explores the benefits of this approach, and the prospects for increasing it further.

Blobs: multi-dimensional gas in Dencun

At the start of this year, the average block was 150 kB in size. A large fraction of that size is rollup data: layer 2 protocols storing data on chain for security. This data was expensive: even though transactions on rollups would cost ~5-10x less than corresponding transactions on the Ethereum L1, even that cost was too high for many use cases.

Why not decrease the calldata gas cost (currently 16 gas per nonzero byte and 4 gas per zero byte), to make rollups cheaper? We did this before, we could do it again. The answer here is: the worst-case size of a block was 30,000,00016=1,875,000 nonzero bytes, and the network already can barely handle blocks of that size. Reducing costs by another 4x would raise the maximum to 7.5 MB, which would be a huge risk to safety.

This problem ended up being handled by introducing a separate space of rollup-friendly data, known as "blobs", into each block. The two resources have separate prices and separate limits: after the Dencun hard fork, an Ethereum block can contain at most (i) 30 million gas, and (ii) 6 blobs, which can contain ~125 kB of calldata each. Both resources have separate prices, adjusted by separate EIP-1559-like pricing mechanisms, targeting an average usage of 15 million gas and 3 blobs per block.

As a result, rollups have become 100x cheaper, transaction volume on rollups increased by more than 3x, and the theoretical maximum block size was only increased slightly: from ~1.9 MB to ~2.6 MB.

Transaction fees on rollups, courtesy of growthepie.xyz. The Dencun fork, which introduced blobs with multidimensional pricing, happened on 2024 Mar 13.

Multi-dimensional gas and stateless clients

In the near future, a similar problem will arise regarding storage proofs for stateless clients. Stateless clients are a new type of client which will be able to verify the chain without storing much or any data locally. Stateless clients do this by accepting proofs of the specific pieces of Ethereum state that transactions in that block need to touch.

A stateless client receives a block, together with proofs proving the current values in the specific parts of the state (eg. account balances, code, storage) that the block execution touches. This allows a node to verify a block without having any storage itself.

A storage read costs 2100-2600 gas depending on the type of read, and storage writes cost more. On average, a block does something like 1000 storage reads and writes (including ETH balance checks, SSTORE and SLOAD calls, contract code reading, and other operations). The theoretical maximum, however, is 30,000,0002,100=14,285 reads. A stateless client's bandwidth load is directly proportional to this number.

Today, the plan is to support stateless clients by moving Ethereum's state tree design from Merkle Patricia trees to Verkle trees. However, Verkle trees are not quantum-resistant, and are not optimal for newer waves of STARK proving systems. As a result, many people are interested in supporting stateless clients through binary Merkle trees and STARKs instead - either skipping Verkle entirely, or upgrading a couple of years after the Verkle transition once STARKs become more mature.

STARK proofs of binary hash tree branches have many advantages, but they have the key weakness that proofs take a long time to generate: while Verkle trees can prove over a hundred thousand values per second, hash-based STARKs can typically prove only a couple thousand hashes per second, and proving each value requires a "branch" containing many hashes.

Given the numbers that are being projected today from hyper-optimized proof systems such as Binius and Plonky3 and specialized hashes like Vision-Mark-32, it seems likely that we will for some time be in a regime where it's practical to prove 1,000 values in less than a second, but not 14,285 values. Average blocks would be fine, but worst-case blocks, potentially published by an attacker, would break the network.

The "default" way we have handled such a scenario is re-pricing: make storage reading more expensive to reduce the per-block maximum to something safer. However, we have already done this many times, and it would make too many applications too expensive to do this again. A better approach would be multidimensional gas: limit and charge for storage access separately, keeping the average usage at 1,000 storage accesses per block but setting a per-block limit of eg. 2,000.

Multidimensional gas more generally

One other resource that is worth thinking about is state size growth: operations that increase the size of the Ethereum state, which full nodes will need to hold from then on. The unique property of state size growth is that the rationale from limiting it comes entirely from long-run sustained usage, and not spikes. Hence, there may be value in adding a separate gas dimension for state size increasing operations (eg. zero-to-nonzero SSTORE, contract creation), but with a differnet goal: we could set a floating price to target a specific average usage, but set no per-block limit at all.

This shows one of the powerful properties of multidimensional gas: it lets us separately ask the questions of (i) what is the ideal average usage, and (ii) what is the safe per-block maximum usage, for each resource. Rather than setting gas prices based on per-block maximums, and letting average usage follow, we have 2𝑛 degrees of freedom to set 2𝑛 parameters, tuning each one based on what is safe for the network.

More complicated situations, like where two resources have safety considerations that are partially additive, could be handled by making an opcode or resource cost some quantity of multiple types of gas (eg. a zero-to-nonzero SSTORE could cost 5000 stateless-client-proof gas and 20000 storage-expansion gas).

Per-transaction max: the weaker-but-easier way to get multidimensional gas

Let 𝑥1 be the gas cost of data and 𝑥2 be the gas cost of computation, so in a one-dimensional gas system we can write the gas cost of a transaction:

𝑔𝑎𝑠=𝑥1∗𝑑𝑎𝑡𝑎+𝑥2∗𝑐𝑜𝑚𝑝𝑢𝑡𝑎𝑡𝑖𝑜𝑛

In this scheme, we instead define the gas cost of a transaction as:

𝑔𝑎𝑠=𝑚𝑎𝑥(𝑥1∗𝑑𝑎𝑡𝑎,𝑥2∗𝑐𝑜𝑚𝑝𝑢𝑡𝑎𝑡𝑖𝑜𝑛)

That is, instead of a transaction being charged for data plus computation, the transaction gets charged based on which of the two resources it consumes more of. This can easily be extended to cover more dimensions (eg. 𝑚𝑎𝑥(...,𝑥3∗𝑠𝑡𝑜𝑟𝑎𝑔𝑒_𝑎𝑐𝑐𝑒𝑠𝑠)).

It should be easy to see how this improves throughput while preserving safety. The theoretical max amount of data in a block is still 𝐺𝐴𝑆𝐿𝐼𝑀𝐼𝑇𝑥1, exactly the same as in the one-dimensional gas scheme. Similarly, the theoretical max amount of computation is 𝐺𝐴𝑆𝐿𝐼𝑀𝐼𝑇𝑥2, again exactly the same as in the one-dimensional gas scheme. However, the gas cost of any transaction that consumes both data and computation decreases.

This is approximately the scheme employed in the proposed EIP-7623, to reduce maximum block size while increasing blob count further. The precise mechanism in EIP-7623 is slightly more complicated: it keeps the current calldata price of 16 gas per byte, but it adds a "floor price" of 48 gas per byte; a transaction pays the higher of (16 * bytes + execution_gas) and (48 * bytes). As a result, EIP-7623 decreases the theoretical max transaction calldata in a block from ~1.9 MB to ~0.6 MB, while leaving the costs of most applications unchanged. The benefit of this approach is that it is a very small change from the current single-dimensional gas scheme, and so it is very easy to implement.

There are two drawbacks:

  1. Transactions that are heavy on one resource are still needlessly charged a large amount, even if all the other transactions in the block use little of that resource.
  2. It creates incentives for data-heavy and computation-heavy transactions to merge together into a bundle to save costs.

I would argue that an EIP-7623-style rule, both for transaction calldata and for other resources, can bring large-enough benefits to be worth it even despite these drawbacks. However, if and when we are willing to put in the (significantly higher) development effort, there is a more ideal approach.

Multidimensional EIP-1559: the harder-but-ideal strategy

Let us first recap how "regular" EIP-1559 works. We will focus on the version that was introduced in EIP-4844 for blobs, because it's mathematically more elegant.

We track a parameter, excess_blobs. During each block, we set:

excess_blobs <-- max(excess_blobs + len(block.blobs) - TARGET, 0)

Where TARGET = 3. That is, if a block has more blobs than the target, excess_blobs increases, and if a block has less than the target, it decreases. We then set blob_basefee = exp(excess_blobs / 25.47), where exp is an approximation of the exponential function 𝑒𝑥𝑝(𝑥)=2.71828𝑥.

That is, whenever excess_blobs increases by ~25, the blob basefee increases by a factor of ~2.7. If blobs get too expensive, average usage drops, and excess_blobs starts decreasing, automatically dropping the price again. The price of a blob constantly adjusts to make sure that on average, blocks are half full - that is, they contain an average of 3 blobs each.

If there is a short term spike in usage, then the limit kicks in: each block can only contain a maximum of 6 blobs, and in such a circumstance transactions can compete with each other by bidding up their priority fees. In the normal case, however, each blob only needs to pay the blob_basefee plus a tiny extra priority fee as an incentive to get included at all.

This kind of pricing existed in Ethereum for gas for years: a very similar mechanism was introduced with EIP-1559 back in 2020. With EIP-4844, we now have two separately floating prices for gas and for blobs.

Gas base fee over the course of one hour on 2024-05-08, in gwei. Source: ultrasound.money.

In principle, we could add more separately-floating fees for storage reading, and other kinds of operations, though with one caveat that I will expand on in the next section.

For users, the experience is remarkably similar to today: instead of paying one basefee, you pay two basefees, but your wallet can abstract that away from you and just show you the expected fee and maximum fee that you can expect to pay.

For block builders, most of the time the optimal strategy is the same as today: include anything that is valid. Most blocks are not full - neither in gas nor in blobs. The one challenging case is when there is enough gas or enough blobs to exceed the block limit, and the builder needs to potentially solve a multidimensional knapsack problem to maximize its profit. However, even there pretty good approximation algorithms exist, and the gains from making proprietary algorithms to optimize profits in this case are much smaller than the gains from doing the same with MEV.

For developers, the main challenge is the need to redesign features of the EVM, and its surrounding infrastructure, that is designed around one price and one limit today into a design that accommodates multiple prices and multiple limits. One issue for application developers is that optimization becomes slightly harder: in some cases, you can no longer unambiguously say that A is more efficient than B, because if A uses more calldata but B uses more execution, then A might be cheaper when calldata is cheap, and more expensive when calldata is expensive. However, developers would still be able to get reasonably good results by optimizing based on long-run historical average prices.

Multidimensional pricing, the EVM and sub-calls

There is one problem that did not appear with blobs, and will not appear with EIP-7623 or even a "full" multidimensional pricing implementation for calldata, but will appear if we try to separately price state accesses, or any other resource: gas limits in sub-calls.

Gas limits in the EVM exist in two places. First, each transaction sets a gas limit, which caps the total amount of gas that can be used in that transaction. Second, when a contract calls another contract, the call can set its own gas limit. This allows contracts to call other contracts that they do not trust, and still guarantee that they will have gas left over to perform other computations after that call.

A trace of an account abstraction transaction, where an account calls another account, and only gives the callee a limited amount of gas, to ensure that the outer call can keep running even if the callee consumes the entire gas that was assigned to it.

The challenge is: making gas multidimensional between different types of execution seems like it would require sub-calls to provide multiple limits for each type of gas, which would require a really deep change to the EVM, and would not be compatible with existing applications.

This is one reason why multidimensional gas proposals often stop at two dimensions: data and execution. Data (whether transaction calldata or blobs) is only assigned outside the EVM, and so nothing inside the EVM needs to change to make calldata or blobs separately priced.

We can think of an "EIP-7623-style solution" to this problem. Here is one simple implementation: during execution, charge 4x more for storage operations; to simplify the analysis, let's say 10000 gas per storage operation. At the end of the transaction, refund min(7500 * storage_operations, execution_gas). The result would be that, after subtracting out the refund, a user is charged:

execution_gas + 10000 * storage_operations - min(7500 * storage_operations, execution_gas)

Which equals:

max(execution_gas + 2500 * storage_operations, 10000 * storage_operations)

This mirrors the structure of EIP-7623. Another way to do it is to track storage_operations and execution_gas in real time, and charge either 2500 or 10000 depending on how much max(execution_gas + 2500 * storage_operations, 10000 * storage_operations) goes up at the time that the opcode is called. This avoids the need for transactions to over-allocate gas that they will mostly get back through refunds.

We don't get fine-grained permissioning for sub-calls: a sub-call could consume all of a transaction's "allowance" for cheap storage operations. But we do get something good enough, where a contract making a sub-call can set a limit and ensure that once the sub-call finishes executing, the main call still has enough gas to do whatever post-processing it needs to do.

The easiest "full multidimensional pricing solution" that I can think of is: we treat sub-call gas limits as being proportional. That is, suppose that there are 𝑘 different types of execution, and each transaction sets a multi-dimensional limit 𝐿1...𝐿𝑘. Suppose that, at the current point in execution, the remaining gas is 𝑔1...𝑔𝑘. Suppose that a CALL opcode is called, with sub-call gas limit 𝑆. Let 𝑠1=𝑆, and then 𝑠2=𝑠1𝑔1∗𝑔2, 𝑠3=𝑠1𝑔1∗𝑔3, and so on.

That is, we treat the first type of gas (realistically, VM execution) as being a kind of privileged "unit of account", and then assign the other types of gas so that the sub-call gets the same percentage of available gas across each type. This is somewhat ugly, but it maximizes backwards-compatibility. If we want to make the scheme more "neutral" between different types of gas, at the cost of sacrificing backwards-compatibility, we could simply have the sub-call gas limit parameter represent a fraction (eg. [1...63] / 64) of the remaining gas in the current context).

In either case, however, it's worth stressing that once you start introducing multidimensional execution gas, the inherent level of ugliness increases, and this seems difficult to avoid. Hence, our task is to make a complicated tradeoff: do we accept somewhat more ugliness at the EVM level, in order to safely unlock significant L1 scalability gains, and if so, which specific proposal works best for protocol economics and application developers? Quite likely, it is neither of the ones I mentioned above, and there is still room to come up with something more elegant and better.

Comments

All Comments

Recommended for you

  • Putin: Russia "supports" Harris, calls her smile "contagious"

    According to foreign media such as TASS and Russia's Sputnik News, Jinse Finance reported that on the afternoon of September 5th local time, Russian President Putin said at the plenary session of the Eastern Economic Forum 2024 that Russia will "support" the US Democratic Party presidential candidate and vice president Harris as recommended by the US President Biden in the upcoming US presidential election. When asked how he viewed the 2024 US election, Putin said it was the choice of the American people. The new US president will be elected by the American people, and Russia will respect the choice of the American people. Putin also said that just as Biden suggested his supporters to support Harris, "we will do the same, we will support her." The report said that Putin also joked that Harris' laughter is "expressive and infectious," which shows that "she is doing everything well." He added that this may mean that she will avoid further sanctions against Russia.

  • An ETH whale repurchased 5,153 ETH with 12.23 million USDT 20 minutes ago

    A certain high-frequency trading ETH whale monitored by on-chain analyst Yu Jin bought 5,153 ETH with 12.23 million USDT 20 minutes ago.

  • CFTC: Uniswap Labs has actively cooperated with the investigation and only needs to pay a fine of US$175,000

    The CFTC has filed a lawsuit against Uniswap Labs and reached a settlement. It was found that Uniswap Labs illegally provided leveraged or margined retail commodity transactions of digital assets through a decentralized digital asset trading protocol. Uniswap Labs was required to pay a civil penalty of $175,000 and cease violations of the Commodity Exchange Act (CEA). The CFTC acknowledged that Uniswap Labs actively cooperated with law enforcement agencies in the investigation and reduced the civil penalty.

  • Federal Reserve Beige Book: Respondents generally expect economic activity to remain stable or improve

    The Federal Reserve's Beige Book pointed out that economic activity in three regions has slightly increased, while the number of regions reporting flat or declining economic activity has increased from five in the previous quarter to nine in this quarter. Overall employment levels remain stable, although some reports indicate that companies are only filling necessary positions, reducing working hours and shifts, or reducing overall employment levels through natural attrition. However, reports of layoffs are still rare. Generally speaking, wage growth is moderate, and the growth rate of labor input costs and sales prices ranges from slight to moderate. Consumer spending has declined in most regions, while in the previous reporting period, consumer spending remained stable overall.

  • Puffpaw Completes $6 Million Seed Round with Lemniscap Ventures as Participant

    Puffpaw has announced the completion of a $6 million seed round of financing, with participation from Lemniscap Ventures. The Puffpaw project plans to launch a blockchain-enabled electronic cigarette aimed at helping users reduce nicotine intake through token incentives. The project encourages users to quit smoking by recording their smoking habits and rewarding them with tokens. Puffpaw's token economics aims to cover 30% of the cost of users' first month of using their product and provide social rewards. The project also considers possible system abuse, but the issue of users potentially reporting smoking habits dishonestly is not yet clear.

  • Affected by Ethervista and others, Ethereum Gas temporarily rose to 33gwei

    According to Etherscan, due to the influence of contracts such as Ethervista, Ethereum Gas has temporarily risen to 33gwei, with the top three being EthervistaRouter, UniswapRouter, and BananaGun.

  • The probability of the Fed cutting interest rates by 25 basis points in September is 55%.

    The probability of the Federal Reserve cutting interest rates by 25 basis points in September is 55.0%, while the probability of a 50 basis point cut is 45.0%. The probability of the Federal Reserve cutting interest rates by a cumulative 50 basis points by November is 32.1%, by 75 basis points is 49.2%, and by 100 basis points is 18.8%.

  • Nvidia: No subpoena received from the US Department of Justice

    Nvidia (NVDA.O) stated that it has not received a subpoena from the US Department of Justice.

  • US SEC again postpones decision on environmentally friendly Bitcoin ETF listing application

    The US Securities and Exchange Commission (SEC) has once again postponed its final decision on the New York Stock Exchange (NYSE) Arca's application for a carbon offset Bitcoin ETF. According to a document dated September 4th, the decision has been extended to November 21st. The ETF aims to provide a Bitcoin investment exposure in an environmentally friendly way by offsetting carbon emissions, tracking an investment portfolio composed of 80% Bitcoin and 20% carbon credit futures. Tidal Investments submitted the fund registration application in December 2023, while NYSE Arca submitted the initial application in March. Concerns have been raised about the environmental impact of Bitcoin mining, with the International Monetary Fund (IMF) reporting that cryptocurrency mining accounts for 1% of global greenhouse gas emissions. The delay in this decision also includes the postponement of approval for the Nasdaq One-Stop Cryptocurrency Investment Portfolio ETF.

  • Japanese regulator calls for lower cryptocurrency tax rates by 2025

    On September 4th, it was announced that Japan's financial regulatory agency has released a comprehensive tax reform plan for the fiscal year 2025, which includes regulations on cryptocurrency to lower its tax rate.