Should computed beacon-state aggregates be exposed as `BeaconState` fields?

Background. Since EIP-4788, EVM contracts can verify dynamic BeaconState fields via SSZ proofs against the beacon block root. This works for fields that exist as state, but not for values the protocol computes during epoch processing and discards immediately after use. total_active_balance is computed every epoch in process_rewards_and_penalties to scale base rewards, then thrown away. The sum over state.slashings is computed every epoch in process_slashings to determine the slashing penalty multiplier, then thrown away. Neither is reachable from the EL today, even with EIP-4788, because there is no field to prove against.

The only on-chain alternative is to iterate the validator registry, which is infeasible at any reasonable gas cost. The CL’s own slashing penalty formula multiplies sum(state.slashings) by a constant, capped at total_active_balance. A contract that wants to verify or bound slashing penalties on its own validators from first principles needs both values.

My question. Has there been discussion of persisting these aggregates directly in BeaconState so contracts can verify them via the same SSZ proof pattern they already use for individual validator state? Something like:

class BeaconState(Container):
    # ... existing fields ...
    total_active_balance: Gwei
    slashings_sum: Gwei
    num_active_validators: uint64
    base_reward_per_increment: Gwei

The fields would be populated at the end of process_epoch from values the protocol already computes:

state.total_active_balance = get_total_active_balance(state)
state.slashings_sum = sum(state.slashings)
state.num_active_validators = uint64(len(get_active_validator_indices(state, get_current_epoch(state))))
state.base_reward_per_increment = get_base_reward_per_increment(state)

All four functions on the right side already exist in the spec and are called every epoch. No new computation is added. Values that are currently discarded are persisted.

CL-only change. No execution-layer changes are required. Contracts read these values via the existing 4788 + SSZ proof pattern used for any other BeaconState field. State size cost is negligible (a few uint64 fields per epoch). Compute cost is zero, since the values are already computed every epoch and used internally.

Stable indices across forks. If this EIP ships in or after the same fork as EIP-7688, the new fields are added as entries in the BeaconState StableContainer with stable generalized indices. If it ships before 7688, indices stabilize from the eventual 7688 migration onward. Either way, contracts that hardcode generalized indices for these fields are robust to future BeaconState additions.

The motivation. I ran into this trying to bound a validator’s maximum slashing penalty over a window. The penalty depends on the sum of state.slashings over the previous EPOCHS_PER_SLASHINGS_VECTOR epochs and on total_active_balance at the slashing epoch. Both are computed every epoch by the protocol and immediately discarded. Reconstructing them on-chain requires iterating the validator set, which is infeasible. Reading them from an oracle reintroduces a trusted party that the dapp’s value proposition rules out.

The same pattern shows up across slashing-aware applications. The CL’s slashing penalty scales with sum(state.slashings) / total_active_balance, so any contract that prices, insures, or bounds slashing exposure from first principles needs both values. Issuance-tracking products need base_reward_per_increment to follow real-time CL issuance. Stake-weighted governance needs total_active_balance to compute legitimacy thresholds against the full validator set rather than a participating subset.

In every case, the value the application needs is already computed by the protocol every epoch. The protocol uses it once and throws it away. Persisting it costs almost nothing on the CL side and removes an entire category of trusted infrastructure from a class of products where trustlessness is the value proposition.

The principle this EIP would establish is narrow: only values that the protocol already computes during epoch processing qualify for inclusion. Values requiring new computation, or that depend on application-specific parameters (windowing, weighting, etc.), are out of scope and belong in application contracts.

I’d like to know whether this has been proposed before, whether the proposed scope (the four fields above) is the right cut, and whether there are reasons experienced CL researchers would consider this disqualifying.