← All Posts | off-chain | August 25, 2026

Lido – Missing pending top ups in CL pending balance calculation

Paweł Kuryłowicz

Paweł Kuryłowicz

Managing Partner & Smart Contract Security Auditor

Top ups being accounted in the report are ignored in the next frame, lowering the CL pending balance.

Vulnerability Details

The get_pending_lido_validators function only considers pending consensus-layer deposits whose pubkey appears in pending_keys, and that set is built from pending_lido_keys returned by compute_lido_validators: keys that do not yet have a matching validator in the CL validator set.

Pending deposits that are top-ups to existing Lido validators (pubkey already active on the CL) therefore never enter pending_validators and are omitted from any logic that relies on this helper. They are included in the depositedForCurrentReport, a smart contract’s variable for the frame in which the top up was executed. However, this variable is cleared when the new frame comes.

Cross-frame, execution-layer accounting on the core contracts can still reflect deposits that landed in the EL flow for the current reporting frame (e.g. via getBalanceStats– related fields such as amounts attributed to the current report), while the CL queue still holds the corresponding top-up as pending_deposits until it is processed.

If that top-up is not applied within the frame, it remains in pending_deposits on the next frame, but the EL-side depositedForCurrentReport variable does not carry it forward again; at the same time the oracle recomputes “pending CL” via _get_cl_pending_validators_balance, which still drops those top-ups because the pubkeys are not “pending keys”.

Vulnerable scenario

The following steps lead to the described result:

  1. A Lido-used pubkey already has an active validator on the consensus layer, and additional stake is submitted so that a top-up appears in the CL pending_deposits queue.
  2. The oracle correctly builds cl_pending_balance_gwei by calling _get_cl_pending_validators_balance, which only sums deposits for Lido pending pubkeys and the top up is skipped in this function. However, it is included in the Accounting smart contract as the amount being deposited in the current frame (depositedForCurrentReport).
  3. The top up deposit has not been processed within the frame epochs and it’s forwarded in pending_deposits to the next frame epochs.
  4. Neither the next frame’s EL depositedForCurrentReport variable nor the oracle’s _get_cl_pending_validators_balance output includes the top up pending deposit amount.
  5. The CL pending balance is lower than it should be by the deposit amount.

Impact

HIGH – Top ups being accounted in the depositedForCurrentReport for the current report are ignored in the next frame, lowering the CL pending balance.

Recommendation

  • Include pending top ups in the CL pending balance returned by the Oracle.

References

Join the newsletter now

Please wait...

Thank you for sign up!