Skip to content

Network Balances Process Specification

Version

v1.0 - Initial specification

Overview

The network balances process calculates and submits the total user ETH provided by rETH holders, aggregating balances from the deposit pool, minipools, megapools, fee distributors, smoothing pool, and rETH contracts at regular intervals.

Technical Prerequisites

  • Integer Arithmetic: All calculations use big integer arithmetic to avoid floating-point precision errors
  • On-chain State: Process operates on finalized beacon chain epochs to ensure data stability
  • Oracle DAO Membership: Only Oracle DAO members can submit network balances
  • Submission Intervals: Submissions occur at configured intervals (default: configurable via protocol settings)
  • Deterministic Calculations: All calculations must be deterministic across Oracle DAO members, with state snapshots pinned to identical blocks to ensure consistency
  • Consensus Mechanism: Multiple Oracle DAO members submit identical calculations, with protocol consensus determining the accepted values

Scheduling and Target Block Selection

Submission Timing Calculation

The target submission time is calculated using a reference timestamp and configured interval:

submission_timestamp = reference_timestamp + (interval_count * submission_interval_seconds)

Where:

Term Definition
reference_timestamp Off-chain configuration parameter (not on-chain)
submission_interval_seconds rocketDAOProtocolSettingsNetwork.getSubmitBalancesFrequency()
interval_count Number of complete intervals since reference timestamp

Target Block Selection

The process determines the target execution layer block using the submission timestamp:

  1. Calculate target beacon slot:

    target_slot = (submission_timestamp - genesis_time) / seconds_per_slot
    

  2. Find the latest execution block at or before the target slot (searching back up to 32 slots if necessary)

  3. Verify the target epoch is finalised before proceeding

Network Balance Components

The process calculates the following balance components:

1. Deposit Pool Balance

User deposits awaiting minipool assignment.

deposit_pool_balance = rocketDepositPool.getUserBalance()

2. Minipool Balances

For each minipool, calculate user share based on status:

Dissolved Minipools

Status: minipool.status == Dissolved

user_balance = 0

Initialised/Prelaunch Minipools

Status: minipool.status == Initialized || minipool.status == Prelaunch

user_balance = minipool.getUserDepositBalance()

Special Case: Redstone LEB v2 ("Broken" LEBs)

Status: minipool.version == 2 && minipool.getDepositType() == Variable

This case is evaluated before the standard active path, including the inactive validator fallback below.

user_balance = minipool_balance + beacon_balance - node_refund_balance - node_deposit_balance
is_staking = (validator_exists && activation_epoch < current_epoch && exit_epoch > current_epoch)

Where:

Term Definition
beacon_balance Beacon Chain API /eth/v1/beacon/states/{stateId}/validator_balances
minipool_balance ETH balance of minipool
node_refund_balance minipool.getNodeRefundBalance()
node_deposit_balance minipool.getNodeDepositBalance()
validator_exists Beacon Chain API /eth/v1/beacon/states/{stateId}/validators (true if validator data present in response)
activation_epoch, exit_epoch Beacon Chain API /eth/v1/beacon/states/{stateId}/validators
current_epoch Calculated as beacon_slot / slots_per_epoch

Active Minipools (Standard)

Status: minipool.status == Staking

Inactive Validator Fallback

If the validator does not yet exist on the beacon chain, or has not yet activated (activation_epoch >= current_epoch), fall back to the deposit balance:

user_balance = minipool.getUserDepositBalance()
is_staking = false

Active Validator
total_balance = beacon_balance + minipool_contract_balance - node_refund_balance
user_balance = minipool.calculateUserShare(total_balance)
is_staking = (exit_epoch > current_epoch)

Where:

Term Definition
beacon_balance Beacon Chain API /eth/v1/beacon/states/{stateId}/validator_balances
minipool_contract_balance ETH balance of minipool
node_refund_balance minipool.getNodeRefundBalance()
activation_epoch, exit_epoch Beacon Chain API /eth/v1/beacon/states/{stateId}/validators
current_epoch Calculated as beacon_slot / slots_per_epoch
slots_per_epoch Beacon Chain API /eth/v1/config/spec field SLOTS_PER_EPOCH

Special Case: Full Minipools in Refund Queue

Status: minipool.getDepositType() == Full && minipool.getUserDepositBalance() == 0

user_balance = minipool.calculateUserShare(total_balance) - 16 ETH
is_staking = (exit_epoch > current_epoch)
Where:

Term Definition
beacon_balance Beacon Chain API /eth/v1/beacon/states/{stateId}/validator_balances
minipool_contract_balance ETH balance of minipool
node_refund_balance minipool.getNodeRefundBalance()
exit_epoch Beacon Chain API /eth/v1/beacon/states/{stateId}/validators
current_epoch Calculated as beacon_slot / slots_per_epoch
slots_per_epoch Beacon Chain API /eth/v1/config/spec field SLOTS_PER_EPOCH

Aggregated Results:

minipool_total_balance = Σ(user_balance) for all minipools
minipool_staking_balance = Σ(user_balance) for minipools where is_staking == true

3. Megapool Balances

For each megapool:

  1. Calculate Megapool Beacon Balance:

Each megapool validator's beacon balance contribution is determined using smart contract state rather than raw beacon chain balance alone. This is necessary because validators pass through transitory states — such as prestake, pending activation, and exiting — where funds are moving between the execution layer and consensus layer. Using on-chain state from rocketMegapoolManager.getValidatorInfo(index) alongside beacon chain data ensures balance is accounted for continuously and accurately as it transitions between layers.

Validator State Machine:

Validator State Beacon Balance Contribution Staking Balance Contribution
Dissolved / Exited / InQueue Excluded entirely None
InPrestake LastRequestedValue (milli-ETH, converted to wei) None
Exiting, beacon balance == 0 Withdrawal amount recovered from beacon chain withdrawal scan None
Exiting, beacon balance > 0 validator_balance (gwei, converted to wei) validator_balance - reduced_bond
Staked, not yet activated (activation_epoch == FAR_FUTURE_EPOCH or activation_epoch > current_epoch) DepositValue (milli-ETH, converted to wei) None
Staked, active (activation_epoch < current_epoch && exit_epoch > current_epoch) validator_balance (gwei, converted to wei) validator_balance - reduced_bond

Where:

Term Definition
validator_balance Beacon Chain API /eth/v1/beacon/states/{stateId}/validator_balances
activation_epoch, exit_epoch, withdrawable_epoch Beacon Chain API /eth/v1/beacon/states/{stateId}/validators
current_epoch Calculated as beacon_slot / slots_per_epoch
reduced_bond rocketDAOProtocolSettingsNode.getReducedBond() (protocol-governed parameter, currently 4 ETH)
LastRequestedValue, DepositValue On-chain megapool validator info fields (stored in milli-ETH)
Withdrawal scan For exiting validators with zero beacon balance: search from the validator's withdrawable_epoch on the beacon chain to find the withdrawal record and recover the withdrawn amount

Aggregated Results:

megapool_beacon_total = Σ(beacon_balance_contribution) for all megapool validators
megapool_staking_balance = Σ(staking_balance_contribution) for all megapool validators
total_withdrawn_balance = Σ(withdrawn_amount) for exiting validators with zero beacon balance

  1. Calculate User Share:

The reward calculation separates beacon-side balance growth from contract-side pending rewards:

pending_rewards = megapool.getPendingRewards() - total_withdrawn_balance
beacon_balance_increase = megapool_beacon_total - user_capital - node_bond
rewards = beacon_balance_increase + pending_rewards

The reward split is then calculated based on the sign of rewards:

if rewards > 0:
    reward_split = megapool.calculateRewards(rewards)
    reth_rewards = reward_split.reth_rewards
else if rewards < 0 and (rewards + node_bond) < 0:
    // Losses exceed node bond — excess loss attributed to rETH holders
    reth_rewards = rewards + node_bond    // (negative value)
else:
    reth_rewards = 0

user_share = user_capital + reth_rewards

Where:

Term Definition
megapool_beacon_total Sum of beacon balance contributions from step 1
total_withdrawn_balance Sum of recovered withdrawal amounts for exiting validators with zero beacon balance (from step 1)
user_capital megapool.getUserCapital()
node_bond megapool.getNodeBond()
pending_rewards megapool.getPendingRewards() minus total_withdrawn_balance (to avoid double-counting funds already accounted for in the beacon total)
calculateRewards Must be called with state pinned to the target execution layer block number for determinism

Note on Negative Rewards (Loss Sharing): In normal slashing scenarios, the node operator's bond is sufficient to absorb the penalty and rETH holders are unaffected. Excess loss reaching rETH holders would only occur in a network-wide correlated slashing event — an extremely unlikely scenario. Even then, the node bond provides a buffer, and only losses beyond that bond are reflected as a negative reth_rewards value, reducing the megapool's user share below user_capital.

Aggregated Results:

megapool_user_share_total = Σ(user_share) for all megapools
megapool_staking_balance = Σ(staking_balance_contribution) for all active megapool validators

4. Fee Distributor Balances

For each registered node, resolve its fee distributor address and sum the user share of its balance:

for i in 0..rocketNodeManager.getNodeCount():
    nodeAddress = rocketNodeManager.getNodeAt(i)
    distributor_address = rocketNodeDistributorFactory.getProxyAddress(nodeAddress)
    distributor_share_total += distributor.getUserShare()

5. Smoothing Pool Share

Estimated portion of smoothing pool balance attributable to rETH holders using rewards tree generation logic.

smoothing_pool_share = approximate_staker_share_of_smoothing_pool()

6. rETH Contract Balance

ETH held directly in the rETH token contract.

reth_contract_balance = rocketTokenRETH.balance

7. rETH Supply

Total rETH tokens in circulation.

reth_supply = rocketTokenRETH.totalSupply()

8. Node Credit Balance

Total deposit credits owed to node operators.

for i in 0..rocketNodeManager.getNodeCount():
    nodeAddress = rocketNodeManager.getNodeAt(i)
    node_credit_balance += rocketNodeDeposit.getNodeDepositCredit(nodeAddress)

Final Calculations

Total ETH Calculation

total_eth = deposit_pool_balance
          + minipool_total_balance
          + megapool_user_share_total
          + distributor_share_total
          + smoothing_pool_share
          + reth_contract_balance
          - node_credit_balance

Exchange Rate Calculation

exchange_rate = total_eth / reth_supply

Where:

Term Definition
reth_supply rocketTokenRETH.totalSupply()

Exchange Rate Clamping (Saturn)

When Saturn is deployed, the exchange rate change is clamped to a governance-set maximum to prevent sudden rate spikes or drops (e.g. from oracle errors or manipulation):

max_reth_delta = rocketDAOProtocolSettingsNetwork.getMaxRethDelta()
last_submission_rate = rocketNetworkBalances.getRETHExchangeRate()

rate_change = exchange_rate - last_submission_rate

if rate_change > max_reth_delta:
    clamped_rate = last_submission_rate + max_reth_delta
    submitted_total_eth = total_eth * clamped_rate / exchange_rate
else if rate_change < -max_reth_delta:
    clamped_rate = last_submission_rate - max_reth_delta
    submitted_total_eth = total_eth * clamped_rate / exchange_rate
else:
    submitted_total_eth = total_eth

The value actually submitted on-chain is submitted_total_eth, not the raw total_eth.

Total Staking ETH Calculation

total_staking_eth = minipool_staking_balance + megapool_staking_balance

Staking ETH Guard

After clamping, the staking balance cannot exceed the submitted total ETH:

if submitted_total_eth < total_staking_eth:
    total_staking_eth = submitted_total_eth

Submission Process

Data Validation

  1. Check if identical balances were previously submitted for the target block
  2. Verify submission hasn't occurred too recently based on configured interval

Smart Contract Submission

The process calls the rocketNetworkBalances.submitBalances() function with: - block: Target execution block number - slotTimestamp: Calculated submission timestamp - totalEth: Clamped total ETH value (submitted_total_eth — may differ from raw total_eth if rate clamping was applied) - stakingEth: Total staking ETH value (after staking guard applied) - rethSupply: Current rETH token supply