Methodology
How the proposed ZERO_VALUE_TRANSFER and VALUE_TRANSFER
values on the dashboard are derived — from raw benchmark runtimes through model
fitting to the worst-case gas selection.
EIP-2780 background
Today every transaction pays a flat 21 000 gas base cost, whether or not it moves ether — a value transfer and a zero-value transfer cost exactly the same. EIP-2780 asks whether that single price still reflects the real work. Rather than fit one blended model, this dashboard measures the end-to-end cost of each kind of transfer directly and reports three values:
-
ZERO_VALUE_TRANSFER— the full per-transaction cost of a transfer that moves no ether. Re-priced against today's 21 000. -
VALUE_TRANSFER— the full per-transaction cost of a transfer that does move ether. Also compared against today's flat 21 000, since a value transfer never paid a separate value charge at the transaction level. -
TX_VALUE_COST— the marginal cost of moving value,VALUE_TRANSFER − ZERO_VALUE_TRANSFER. Compared against 9 000, the cost of aCALLthat forwards value (G_callvalue), as the natural reference point for moving value. See the derivation below.
These constants were set long ago and may no longer reflect the real execution cost on modern clients. This analysis measures the actual runtime of each transfer kind and re-prices it against a fixed throughput anchor.
The receiver cases
The benchmark test test_ether_transfers_onchain_receivers sends ether to
several kinds of receiver. Each is a separate case_id because the client's
work differs by receiver type. The dashboard shows a short readable label for each;
the raw case_id below stays the canonical key in
data/results.json. Contract receivers (all but the last three) execute
on-chain code; the 24 KiB variants pad the account to the code-size limit used for
this repricing benchmark.
-
Contract (
diff_to_contract) — transfers to deployed contracts that share a commoncodeHash, so clients can cache the code lookup across transactions. -
Contract (minimal) (
diff_to_contract_minimal) — transfers to a minimal contract whose code is a singleSTOP. -
Contract (24KB, same code)
(
diff_to_contract_same_max) — transfers to contracts padded to 24 KiB whose copies are byte-identical, so clients can cache the code lookup. -
Contract (24KB, unique code)
(
diff_to_contract_diff_max) — transfers to 24 KiB contracts whose copies are each unique (their own address embedded), forcing a fresh code read per transaction. -
Contract (jumpdest)
(
diff_to_unique_code_jumpdest_contract) — transfers to contracts with unique code thatJUMPto the 24 KiB code-size limit, forcing a fresh code read plus a JUMPDEST analysis on every transaction. -
Delegated (24KB, unique code)
(
diff_to_delegated_contract_diff) — transfers to a delegated EOA (EIP-7702) that delegates to a unique 24 KiB contract. -
EOA (
diff_to_existent) — transfers to externally owned accounts (EOAs) that already exist in state. -
Non-existent (
diff_to_nonexistent) — transfers to EOAs that do not yet exist, so the client must create a new account. -
Self (
diff_to_self) — transfers where the sender is its own receiver.
Which cases are charted. Every case above is fit and reported in full
on the detail page and the model
fit page. Two are held out of the Dashboard's charts:
Contract (jumpdest) and Contract
(diff_to_contract) are excluded from the Dashboard's charts, its Summary
goal table and its worst-case highlight, and Contract is excluded from the
Trends page as well — so the Trends page still charts
Contract (jumpdest) while the Dashboard does not.
Deriving opcount (the “opcode trick”)
The model needs the number of value-transfer transactions packed into each benchmark
block (opcount). The harness does not yet emit that count directly, so it
is reconstructed — from a per-transaction opcode in the execution trace where
one exists, and otherwise from the block gas limit and the transaction's EIP-2780
cost. This is a temporary technique — once the harness emits a
per-block transaction count, that field should replace it.
-
Jumping contract cases (Contract & Contract (jumpdest) —
diff_to_contract,diff_to_unique_code_jumpdest_contract): each transaction executes exactly oneJUMP, so theJUMPopcode count from the trace equals the transaction count —opcount = JUMP. -
STOP-only contract cases (Contract (minimal), the 24 KiB
variants & Delegated —
diff_to_contract_minimal,diff_to_contract_same_max,diff_to_contract_diff_max,diff_to_delegated_contract_diff): these do notJUMP, but each transaction executes exactly oneSTOP, so the count falls back toopcount = STOP(verified equal toJUMPon cases that emit both). -
EOA cases (EOA, Non-existent & Self —
diff_to_existent,diff_to_nonexistent,diff_to_self): plain transfers run no opcodes, so the trace has no rows for them. Because these blocks are packed under EIP-2780 pricing, the count isopcount = floor(block_gas_limit / per_tx_gas), whereper_tx_gasis the transaction's EIP-2780 cost rather than a flat 21000: 12,000 for a self-transfer, 15,000 for a zero-value transfer to an EOA, 21,000 for a value transfer to an existing EOA, and 183,600 for a value transfer that creates a new account (the new-account state-gas charge dominates). A flat 21000 would mis-count these blocks and mis-scale their recovered gas.
The NNLS model
For each (client, case_id) group we split the runs by
transfer_amount (the 0/1 indicator for whether the transfer moves ether)
and fit two independent non-negative least squares (NNLS) models,
each with the simple design matrix [1, opcount]:
- the
opcountslope on thetransfer_amount = 0runs →ZERO_VALUE_TRANSFER(per-transaction cost of a plain transfer); - the
opcountslope on thetransfer_amount = 1runs →VALUE_TRANSFER(per-transaction cost of a value transfer).
Fitting the two regimes separately lets each have its own intercept and its own slope, rather than forcing a shared base with a single additive value surcharge. Non-negativity is enforced because gas costs cannot be negative. Inference uses bootstrap resampling (1000 iterations) to produce percentile-based 95% confidence intervals and p-values, since the NNLS constraint breaks the usual closed-form ordinary-least-squares standard errors.
Reading the p-values. These are not conventional two-sided
null-hypothesis p-values. To match the non-negativity constraint, each p-value is the
fraction of the 1000 bootstrap resamples in which the coefficient collapses to
zero — a one-sided boundary test. The bootstrap distribution is centered on
the fitted estimate, so a coefficient sitting well above zero is reproduced as
positive in every resample and the p-value reads 0 (the resolution
floor is 1/1000 = 0.001). A value of 1 means NNLS pinned the
coefficient to exactly zero. Because opcount is a strong, clearly
non-zero driver in both regimes, almost every p-value here is 0 — that is the
expected outcome, the inverse of the usual p-value intuition. The p-value cannot
distinguish a barely-positive coefficient from a large one; use the
confidence intervals for that.
The 100 Mgas/s anchor and worst-case selection
Each runtime coefficient (in milliseconds) is converted to gas at a fixed throughput anchor of 100 Mgas/s:
new_gas = ceil(anchor_rate × runtime_ms / 1e3)
The same conversion (with ceil) is applied to the confidence-interval
bounds. For each parameter the worst case is the
(client, case) with the largest proposed gas
(idxmax on new_gas_rounded) — the most conservative
re-pricing that still keeps every client within the anchor. Worst cases whose
driving model has R² ≤ 0.5, or whose coefficient
p-value > 0.05 (not statistically significant), are flagged as
caveats on the dashboard.
Marginal value cost (TX_VALUE_COST)
The first two parameters price each transfer kind end-to-end. The third isolates
what moving value actually costs:
TX_VALUE_COST = VALUE_TRANSFER − ZERO_VALUE_TRANSFER.
This is the analogue of the old VALUE_GAS surcharge, but it is now a
derived difference of two independent fits rather than a single fitted
coefficient.
Because the two slopes come from separate fits on disjoint data,
their uncertainties add rather than partially cancel (there is no shared bootstrap
covariance to exploit). We propagate the 95% interval by interval arithmetic on the
difference — [with_low − without_high,
with_high − without_low] — and clamp the estimate and both
bounds at zero: a value transfer modeled as cheaper than a plain one is noise,
not a negative gas cost. The difference is flagged with a p-value caveat if
either underlying slope is not statistically significant, and its R² is
the worse of the two regime fits.
A note on VALUE_TRANSFER. Earlier versions of this
dashboard derived VALUE_TRANSFER as TX_BASE + VALUE_GAS
summed within one combined fit. It is now fit directly as the
opcount slope on the value-transfer runs — the same name, but a
measured end-to-end cost rather than a sum.
Reproducing this analysis
End to end, from raw data to the deployed site:
- Obtain a Benchmarkoor API token and place it in
secrets.jsonas{"BENCHMARKOOR_TOKEN": "bmk_..."}(gitignored). - Run
make fetch— pulls the pinned benchmark suite viabenchmarkoor-fetchintodata/raw/. - Run
make analyze— runs the NNLS pipeline and writesdata/results.json. - Run
make site— renders this static site intodocs/.
Or simply make fetch && make analyze && make site.
The committed data/results.json is the auditable record; everything on
these pages is rendered from it at build time.