Starknet Costs and Fees

Introduction

Starknet has operating costs on one hand and transaction fees on the other. Currently, the main goal of Starknet’s transaction fee design is to cover operating costs without any profit at users’ expense. In this post we will explore the complexity around this design goal and how Starknet tackles it. For simplicity, we will focus on L1 costs throughout and disregard cloud costs.

The post is divided as follows: part I explains the complexity, while part II gives concrete formulas for Starknet fees vs costs as a function of system parameters and demand.

For the detailed cost structure and the formulas for costs vs fees, skip to part II.

Part I: design challenges

Covering operating costs without any profit means we aim to be at net zero margin. To that end, the obvious proposal is to charge each transaction a fee equal to its operating cost. So let us begin with a fundamental question: if the operating cost of a transaction is X, why not just charge it a transaction fee of X?

The answer is that some operating costs are not marginal: they are not derived from individual transactions. Instead these costs derive from Ethereum interaction, namely proof verification and state updates. Their key property is a fixed gas cost per action, independent of the very large number of Starknet transactions being compressed by Starknet. The proposal in the fundamental question covers marginal costs but neglects fixed costs, and is therefore refuted.

We arrive at a new task: design transaction fees to cover both marginal and fixed costs at a net zero profit. But how to distribute fixed costs over transactions? The key observation is that every action incurring a fixed cost is triggered by hitting some limiting system parameter. For example, past Starknet blocks were always closed due to reaching a compute limit. How does this observation help? Well, if fixed costs are always triggered by a compute limit then we can associate the fixed cost with a certain amount of compute units. This leads to a natural price per compute unit that would perfectly cover fixed costs: the fixed cost divided by the limit amount of computation. For example, if the total fixed cost is 100 gas, and each block is closed at a limit of 10 compute steps, then the compute fee should be 10 gas/step. Generalizing, we find the following principle: the cost per unit of the limiting resource equals the fixed cost divided by its limit amount. Since we aim for net zero profit, the fee per unit of the limiting resource should equal its cost. In essence, a limiting resource returns us to the marginal setting: each transaction should cover its “limiting resource cost”.

To summarize, we have arrived at the following proposal: the fee for a transaction should equal the sum of its marginal cost and its limiting resource cost. The journey appears complete: we have described a formula for transaction fees that covers operating costs at net zero profit. But alas, we come to a major hurdle:

  1. The limit amounts of various limiting resources are system parameters that often increase between Starknet versions: if we can batch more Starknet transactions per L1 operation then there’s no reason to wait for the next version – it’d just be a waste of resources. For example, increased block compute limits means that gas costs which are fixed per block are triggered after more computation than before.
  2. Such changes alter the cost per unit of the limiting resource.
  3. Transaction fee changes are coupled to Starknet versions, so the fee per unit cannot always match the cost per unit. Why the coupling? Because every fee change requires full node upgrades, and we want to minimize the resulting upgrade overhead.

The consequences of this disparity depend a lot on the demand for the limiting resources, which can lead to overcharging users or undercharging them (i.e operating losses).

Let us illustrate by example. Consider an intermediate block step limit increase between two Starknet versions, as in the following picture. Certainly the fee per step should be somewhere between the costs per step C/L₁ and C/L₂. But where? If most of the demand comes before the increase, then the cost is dominated by the higher cost per step, whence the fee should be around C/L₁. On the other hand, if most of the demand comes after the increase, then cost is dominated by the lower cost per step, whence the fee should be around C/L₂.

We have seen how frequent changes to limit amounts vs infrequent changes to transaction fees introduce risk of non-zero net margin due to unpredictable demand. From here on there are two approaches to take:

  1. A conservative approach, where fees are set to cover the current operating costs until the next version. In this approach, intermediate cost reductions are not transmitted to users until the next version. Hence, the system may enter a positive profit margin upon any intermediate cost reduction. In other words: users overpay.

  2. A more user-oriented approach, where fees are set to cover the estimated operating costs until the next version, taking intermediate cost reductions into account. This approach has the drawback of incurring risk on the Starknet operator: misestimations result in losses, and these estimations are no science!

    1. As we shall see in part II of the post, the unpredictability is exacerbated by the fact Starknet overcharges DA fees. We heuristically undercharge compute fees to counteract overcharging for DA.
    2. The misestimation risk is bounded by a very important dependence on demand: if resource limits are sufficiently high for full block fees to cover costs, then fees will cover costs given enough demand. In other words, assuming sufficiently high limits, enough TPS ensures transaction fees will cover operating costs.

Starknet takes the user-oriented approach.

Part II: fee and cost formulas

Structure of operating costs

Marginal costs

Marginal, or per-tx costs, are clearly mapped to individual transactions. The canonical almost-example is DA (data availability) costs: if a Starknet transaction writes to a storage cell in the state, it incurs an operating cost of sending this update to Ethereum. Here, cost is in units of Ethereum gas. Why almost-example then? Because many writes to a particular storage cell within the same Starknet block will still result in just one update to Ethereum: the final new value. The second example of marginal operating costs are those of handling L1/L2 messages.

Fixed costs

Non-marginal costs, i.e ones that cannot be directly mapped to transactions, will be referred to as fixed costs. There are two granularities of fixed costs in Starknet: fixed-per-block and fixed-per-train. Before recounting them, we should explain what a ‘train’ is? A train is a batch of blocks that is jointly verified by a single STARK proof verification on Ethereum.

Fixed per SHARP train

The gas cost of verifying a proof of a train is fixed at a total of roughly 6M gas per train.

  1. 3M gas
    1. 8x Verify FRI
    2. 3x Verify Merkle
  2. 2M gas for execution of verifier – part of Verify Proof and Register
  3. 1M gas calldata for sending proof – part of Verify Proof and Register

Fixed per Starknet block

There are several sources of fixed-per-block costs, for a total of roughly 215K gas per block

  1. 23K gas per fact registration – part of Verify Proof and Register
  2. 56K gas per SHARP memory page 0
  3. 136K gas per State Update

Fee and cost formulas

The fee per block is given by the following formula, where ‘csp’ is cairo step price, ‘dgp’ is data gas price, and ‘gp’ is gas price. The qualification of block user steps is meant to disregard steps that come from the Starknet operating system.

\begin{aligned}\text{fee per block} & = \text{csp}\times \text{block user steps}+\text{dgp}\times\text{block dg sold} \\ & =\text{gp}\times \underbrace{0.0025}_\text{gas per cs}\times \text{block user steps}+\text{dgp}\times\text{block dg sold} \end{aligned}

The cost per block is slightly more complex. The total amount of gas associated with a block is the sum of 215K fixed-per-block that we have seen above and each block’s fractional part of the fixed-per-train gas. The previous sentence explained the first summand in the following formula. The second is simple: Ethereum transactions must pay for entire blobs, and Starknet the State Update transactions currently use at most one block.

\text{cost per block} = \text{gp}\times\frac{\text{gas-per-train}+\text{train-size}\times \text{gas-per-block}}{\text{train size}}+\text{blob price}

Before computing any numbers, note that Starknet transaction fees charge per write, but its DA costs depend only on the number of memory cells that were updated and not the number of updates to each. Thus Starknet overcharges its DA fees. Note moreover that this margin increases with blocksize, since multiple writes to the same cells are more likely to occur in larger blocks. Note it is possible to resolve DA overcharging by implementing a “DA refund” feature, as in zkSync. In the category of DA fee reduction features, we have prioritized volition over refunds as we believe volition will reduce both fees and costs more significantly. Moreover, volition will also reduce operating costs, unlike DA refunds.

Some numbers and break-even conditions

After the 4844 upgrade, Starknet incurred operating losses. In the first week they were substantial, with over 8 ETH lost per day and less than 20% of costs covered by fees. Ever since, a gradual and ongoing improvement process is bringing costs down. Over the last 5 days the average net loss is <1.5 ETH per day, with fees covering over half the cost (82% on April 1). These improvements are underlain by two major factors:

  1. Block limits increase: the block step limit and blob data limit were both gradually increased. More resources per block increase the fees collected per block.
    • Demand is implicit here: in addition to step and data limits, blocks also have a time limit. If demand is insufficient, then blocks will close due to time limits without containing many resources. If demand is low then the fees collected per will be low regardless of its resource limits.
  2. Data gas price increase, which amplifies the DA margin explained above. On March 28 Starknet was already covering half of its operating costs.

Here are some examples of fees vs costs with given parameters. The crucial factor is demand because it determines the amount of resources contained in a block. Resource prices are also crucial.

First, the current limits are 45M steps, a blob’s worth of felts, and a lifespan of 6 minutes. Let’s start with the following parameters, which are the average data over roughly 20 hours in the last few days.

\text{gp}=20\text{ gwei},\;\text{dgp}=60\text{ gwei},\;\text{block user steps}=22.5\text{M},\;\text{block dg sold}=128\text{K},\;\text{gas per block}=246\text{K},\;\text{train size}=192

we find that fees cover 69% of costs.

\begin{aligned}\text{fee per block} & = 8.8\text{M gwei} \\ \text{cost per block} & =12.8\text{M gwei} \\ \text{fee/cost per block} & = 0.69 \end{aligned}

In practice fees covered closer to 55% of costs, mainly because of a “futures” phenomenon: transaction fees are charged according to gas prices, but operating costs are only paid later at different gas prices. On this particular day, the fluctuations favored users, exacerbating losses.

Greater demand brings more steps and storage writes per block, as represented by the following parameters.

\text{gp}=20\text{ gwei},\;\text{dgp}=60\text{ gwei},\;\text{block user steps}=30\text{M},\;\text{block dg sold}=200\text{K},\;\text{gas per block}=246\text{K},\;\text{train size}=192
In this case we find that fees cover costs with a 6% margin!

\begin{aligned}\text{fee per block} & = 13.5\text{M gwei} \\ \text{cost per block} & =12.8\text{M gwei} \\ \text{fee/cost per block} & = 1.06 \end{aligned}

By configuring a longer block time, we can drive block resources even higher, but that’s a UX trade-off we are constantly thinking about. As for gas and data gas prices, a ratio dgp/gp≈3 seems reasonable when comparing the cost of a (nonzero) byte as 16 gas vs 1 data gas. Indeed a ratio of 3 means that blobs make DA 16/3≈5 times cheaper – similar to the folklore estimates that have been floating around for months.

The tension between block frequency and L1 costs will be resolved by an upcoming feature called applicative recursion. It will both decouple block frequency from L1 costs and moreover reduce L1 costs further, allowing fees to cover costs. Read more about it here!