Draft

Minimal Issuance Reform: Floored Quadratic Offset Tapering

Combining the two earlier variations: the quadratic offset taper gives a smooth pinch-off, and scaling it to 63/64 of its value leaves a small permanent issuance floor. Because the quadratic taper doubles the BASE_REWARD_FACTOR, a 1/64 floor matches the absolute size of the floored linear taper’s 1/32 floor — and because the quadratic taper arrives at saturation tangentially, the floored curve joins the floor with matching slope: a smooth descent rather than a kink.
Published

June 22, 2026

Modified

June 22, 2026

tl;dr

  • The quadratic offset taper brings the net yield to zero tangentially at a 50% staking ratio — a smooth pinch-off rather than the linear taper’s kink;
  • This variant applies the \(\tfrac{1}{64}\) floor scaling of the floored linear taper — halved from \(\tfrac{1}{32}\) because the quadratic taper doubles BASE_REWARD_FACTOR, so the two floors are the same absolute size — to the quadratic offset, leaving a fixed \(\tfrac{1}{64}\) slice of every reward and a small permanent issuance floor of \(\tfrac{1}{64}\) of the untapered issuance, reached at the saturation ratio;
  • Because the quadratic taper meets saturation with zero slope, the floored curve joins the floor \(\tfrac{1}{64}y(f)\) with matching slope — a smooth descent into the floor, where the floored linear taper meets it at an angle;
  • The change to the quadratic proposal is one new constant and a single extra subtraction per duty; the quadratic correction factor and offset helper are reused unchanged.

Introduction

Two earlier variations on the linear offset taper pulled in different directions. The quadratic taper made the pinch-off smoother, replacing the linear offset with a quadratic one so the net yield reaches zero with zero slope at the saturation ratio \(f_\text{sat} = 50\%\). The floored linear taper instead kept issuance positive everywhere, scaling the offset down to \(\tfrac{63}{64}\) of its value to leave a small permanent floor.

This note combines the two: it applies a \(\tfrac{1}{64}\) floor to the quadratic offset. The floor fraction is halved from the floored linear taper’s \(\tfrac{1}{32}\) because the quadratic taper runs at twice the BASE_REWARD_FACTOR, so a \(\tfrac{1}{64}\) slice here is the same absolute size of issuance as a \(\tfrac{1}{32}\) slice there. The natural question is whether the floor disturbs the quadratic taper’s defining property — its smooth, tangential arrival at saturation. It does not. In fact the tangency is exactly what makes the floored version smooth: the net yield descends and joins the floor curve with matching slope, with no kink at the transition. Everything else carries over from the quadratic proposal unchanged.

The Issuance Floor

Recall the quadratic offset fraction, which deducts a fraction \(q(f)\) of each validator’s ideal reward, leaving net yield \((1-q(f))\,y(f)\):

\[q(f) = \tfrac{1}{2}(2f)^{3/2}(5 - 6f), \qquad q(f_\text{sat}) = 1,\]

clamped at \(q = 1\) for \(f \ge f_\text{sat}\). It was constructed so that the net yield reaches zero tangentially at saturation — equivalently, so that \(q'(f_\text{sat}) = 0\). The clamp at \(q = 1\) already imposes a floor — a floor of zero, holding issuance at zero rather than letting the offset exceed the full reward and turn issuance negative. As in the floored linear proposal, we raise that floor to a small positive value: we hold back a fixed slice — here \(\tfrac{1}{64}\) — and apply only \(\tfrac{63}{64}\) of this offset,

\[q_\text{floored}(f) = \frac{63}{64}\,q(f).\]

A fixed \(\tfrac{1}{64}\) slice of every reward then always escapes the offset, so the net yield levels off at \(\tfrac{1}{64}\,y(f)\) rather than reaching zero. The reward curve, the underlying quadratic offset fraction, and the calibration are all unchanged.

Why the descent stays smooth

Write the floored net yield as the unmodified quadratic net yield plus the recovered slice:

\[n(f) = \left(1 - \tfrac{63}{64}q\right)y = \underbrace{(1 - q)\,y}_{\tilde y(f)} \; + \; \tfrac{1}{64}\,q(f)\,y(f).\]

Just below saturation the slope is \(n'(f_\text{sat}^-) = \tfrac{1}{64}\,y'(f_\text{sat}) - \tfrac{63}{64}\,q'(f_\text{sat})\,y(f_\text{sat})\). The quadratic taper’s tangency condition is precisely \(q'(f_\text{sat}) = 0\), so the second term vanishes and

\[n'(f_\text{sat}^-) = \tfrac{1}{64}\,y'(f_\text{sat}).\]

Beyond saturation \(q\) is clamped at 1 and the net yield is exactly the floor, \(n(f) = \tfrac{1}{64}\,y(f)\), whose slope at saturation is \(n'(f_\text{sat}^+) = \tfrac{1}{64}\,y'(f_\text{sat})\) — the same value. The curve therefore meets the floor with continuous slope: a smooth (tangential) join, not a kink. The floored linear taper has no such property — its offset has non-zero slope at saturation, so its net yield meets the floor at an angle.

Note that the join is smooth but not flat: the floor \(\tfrac{1}{64}y(f)\) slopes gently downward (since \(y\) falls as \(f\) rises), and the net yield curve settles tangentially onto that gentle slope rather than arriving horizontally. The quadratic’s tangential pinch-off to zero becomes a tangential settling onto the floor.

Figure 1: Consensus layer net yield: the current curve (B=64), the unmodified quadratic taper (zero floor), and the \(\tfrac{1}{64}\) floored quadratic variant, both compensated at \(B = 256\). The floored curve settles smoothly onto the floor at \(f_\text{sat}\), where the zero-floor curve pinches off to zero.

For \(f \ge f_\text{sat}\) the issuance is the floor fraction times the untapered issuance,

\[\tilde i(f) = \frac{1}{64}\,i(f), \qquad f \ge f_\text{sat},\]

in the same form as the floored linear proposal — the floor depends only on the floor scaling and the clamp at saturation, not on the shape of the taper before it. Because the untapered issuance \(i(f) = f\,y(f)\) rises with \(f\), the floor is not flat: issuance reaches its minimum at saturation, then climbs slowly. For the compensated \(B = 256\) curve the floor is roughly \(0.067\%\) of supply at saturation, rising to \(0.095\%\) at full-supply staking — the same absolute floor as the floored linear taper’s \(\tfrac{1}{32}\) slice at \(B = 128\), since doubling \(B\) and halving the floor fraction cancel. The alternative \(B = 128\) calibration halves it again, to \(\approx 0.034\%\)\(0.047\%\).

Figure 2: Annual ETH issuance for the same curves. The floored variant peaks at \(f \approx 13.0\%\) and is indistinguishable from the zero-floor taper until close to saturation, where the zero-floor curve falls tangentially to zero and the floored one levels off just above it.

The Offset Fraction

In absolute terms — working, as the quadratic proposal does, from the total active balance \(D = f\,S\) and the fixed constant \(D_\text{sat}\) (SATURATION_BALANCE), with \(u = D/D_\text{sat}\) — the quadratic offset fraction is \(q = \tfrac{1}{2}u^{3/2}(5 - 3u) = q_\text{lin}\cdot\tfrac{1}{2}(5 - 3u)\), where \(q_\text{lin} = u^{3/2}\) is the linear proposal’s offset fraction. Scaling by \(\tfrac{63}{64}\) gives

\[q_\text{floored} = \frac{63}{64}\,q_\text{lin}\cdot\tfrac{1}{2}(5 - 3u).\]

Each duty’s offset is thus the linear offset, multiplied by the quadratic per-epoch correction factor \(\tfrac{1}{2}(5 - 3u)\), and then scaled down to \(\tfrac{63}{64}\) — a \(\tfrac{1}{64}\) slice of every reward always survives. The factors of BASE_REWARD_FACTOR cancel, so \(q_\text{floored}\) is independent of \(B\). At saturation \(q = 1\), so \(q_\text{floored} = \tfrac{63}{64}\) — the full reward less the floor slice — and the tangency condition that flattened the zero-floor curve is now what lands the floored curve smoothly on the floor.

Per-duty implementation

The three-part split — an attestation offset on every active validator, a proposer offset on the epoch’s 32 proposers, and a sync offset on the 512 sync-committee members, each deducting \(q_\text{floored}\) times the ideal reward for that duty — is exactly as in the quadratic and linear proposals. Scaling each duty’s offset independently means the floor is preserved duty-by-duty: a validator only attesting in a given epoch still retains its \(\tfrac{1}{64}\) attestation slice.

Consensus Layer Spec

The change to the consensus layer is the quadratic proposal’s, plus a single new constant ISSUANCE_FLOOR_DENOMINATOR \(= 64\) and one extra subtraction per duty. BASE_REWARD_FACTOR is raised and SATURATION_BALANCE is added exactly as before, the get_tapered_offset helper is reused unchanged, and process_issuance_offsets applies the quadratic correction factor \(\tfrac12(5-3u)\) exactly as in the quadratic proposal, then scales each duty’s offset down to \(\tfrac{63}{64}\), removing a \(\tfrac{1}{64}\) slice. Because the quadratic correction already holds the offset at the full reward at and beyond saturation, scaling it places the floor’s onset exactly at \(f_\text{sat}\). The new step uses the same two lookups as the earlier versions: get_beacon_proposer_index_at_slot1 and get_validator_index_by_pubkey.2

Updated Constant and New Constants

Constant Old value New value Notes
BASE_REWARD_FACTOR uint64(64) uint64(256) 4× (crossover \(f \approx 30.8\%\)); \(B = 128\) (crossover \(f \approx 21.0\%\), thinner floor) is the alternative
SATURATION_BALANCE Gwei(60_250_000 * 10**9) Total staked balance \(D_\text{sat}\) at \(f_\text{sat} = 50\%\); set at the hard fork to approximately half the current ETH supply
ISSUANCE_FLOOR_DENOMINATOR uint64(64) The floor \(\tfrac{1}{64}\): each duty retains at least this fraction of its ideal reward. Halved from the floored linear taper’s \(32\) because \(B\) is doubled, matching the absolute floor. A larger power of two gives a thinner floor

New Function: the offset helper

Unchanged from the linear and quadratic proposals. It applies the cubed square-root ratio \(u^{3/2} = (\sqrt{D}/\sqrt{D_\text{sat}})^3\) to a reward as three successive uint64 multiply-divides, so every intermediate stays well within uint64:

def get_tapered_offset(reward: Gwei, sqrt_active: uint64, sqrt_sat: uint64) -> Gwei:
    # reward * (sqrt_active / sqrt_sat)**3 == reward * (D / SATURATION_BALANCE)**(3/2)
    offset = uint64(reward)
    for _ in range(3):
        offset = offset * sqrt_active // sqrt_sat
    return Gwei(offset)

New Step: applying the floored quadratic offset

Identical to the quadratic step, except each duty’s quadratic offset is scaled down to \(\tfrac{63}{64}\)offset - offset // ISSUANCE_FLOOR_DENOMINATOR — leaving a \(\tfrac{1}{64}\) slice that is always paid out. The quadratic correction factor \(\tfrac12(5-3u) = (5\,n_\text{sat} - 3\,n)/(2\,n_\text{sat})\) is computed once and applied per duty exactly as before; the floor subtraction is then a single extra operation on the result:

def process_issuance_offsets(state: BeaconState) -> None:
    # Clamping sqrt_active to sqrt_sat to prevent negative issuance.
    sqrt_sat = integer_squareroot(SATURATION_BALANCE)
    sqrt_active = min(integer_squareroot(get_total_active_balance(state)), sqrt_sat)
    increment = EFFECTIVE_BALANCE_INCREMENT
    base = get_base_reward_per_increment(state)
    epoch = get_current_epoch(state)

    # Quadratic correction (5 - 3 D/D_sat) / 2, as in the quadratic taper.
    n_sat = SATURATION_BALANCE // increment
    n_active = min(get_total_active_balance(state) // increment, n_sat)
    corr_num = 5 * n_sat - 3 * n_active
    corr_den = 2 * n_sat

    # Apply the quadratic correction, then keep a 1/64 floor slice.
    def floored_offset(linear_offset: Gwei) -> Gwei:
        quad = uint64(linear_offset) * corr_num // corr_den
        return Gwei(quad - quad // ISSUANCE_FLOOR_DENOMINATOR)

    # 1. Attester offset — every active validator.
    attest_weight = TIMELY_SOURCE_WEIGHT + TIMELY_TARGET_WEIGHT + TIMELY_HEAD_WEIGHT
    for index in get_active_validator_indices(state, epoch):
        n = state.validators[index].effective_balance // increment
        attest_reward = Gwei(n * base * attest_weight // WEIGHT_DENOMINATOR)
        offset = get_tapered_offset(attest_reward, sqrt_active, sqrt_sat)
        decrease_balance(state, index, floored_offset(offset))

    # 2. Proposer offset — the 32 proposers of the epoch.
    n_total = get_total_active_balance(state) // increment
    ideal_proposer_reward = Gwei(
        n_total * base * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR // SLOTS_PER_EPOCH
    )
    proposer_offset = floored_offset(
        get_tapered_offset(ideal_proposer_reward, sqrt_active, sqrt_sat)
    )
    start_slot = compute_start_slot_at_epoch(epoch)
    for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH):
        proposer = get_beacon_proposer_index_at_slot(state, Slot(slot))
        decrease_balance(state, proposer, proposer_offset)

    # 3. Sync offset — the 512 sync committee members.
    for pubkey in state.current_sync_committee.pubkeys:
        index = get_validator_index_by_pubkey(state, pubkey)
        n = state.validators[index].effective_balance // increment
        sync_reward = Gwei(n * base * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR)
        offset = get_tapered_offset(sync_reward, sqrt_active, sqrt_sat)
        decrease_balance(state, index, floored_offset(offset))

process_issuance_offsets is added to process_epoch immediately after process_rewards_and_penalties and before process_effective_balance_updates, so effective balances still hold the epoch’s values and step 2 charges exactly the proposers selected during the epoch.

The cost is the linear and quadratic proposals’: one O(1)-per-validator deduction (the attestation offset), foldable into the existing rewards/penalties pass, plus two fixed passes over the 32 proposers and 512 sync committee members. The correction multiply-divide keeps linear_offset * corr_num within uint64 for every duty reward, and the floor is a single subtraction per duty, so no wider arithmetic is needed.

Footnotes

  1. The existing get_beacon_proposer_index generalised to an arbitrary slot in the epoch.↩︎

  2. A pubkey→index lookup, which clients already maintain as a cache.↩︎