The document outlines accounting principles and changes accomodated in Lido V2 with the withdrawals support (post Shanghai/Capella hardfork) for the Lido on Ethereum protocol.
Lido protocol total value locked (TVL) is designed to be represented with stETH
token total supply (ERC20-compatible, fungible).
The main component of stETH
token total supply is the balance of Lido-participating validators on the Beacon Chain (now it's Consensus Layer) side. The complete total supply also includes the following additions:
stETH total supply
= beacon balance
+ buffered balance
+ transient balance
Transient balance handling was included with the adoption of LIP-1.
The Merge event added another protocol income source โ execution layer rewards (block proposer accrues priority fee and MEV). Validators participating in the Lido protocol specify these rewards be sent to a separate vault contract. The core Lido contract withdraws the accrued funds as part of each oracle report appending to the buffered balance
. This way, the formula above stays valid: ExecutionLayerRewardsVault
is included into TVL implicitly by Oracle-guided "back-staking", see LIP-12.
With enabled withdrawals, beacon balances
can be split according to the following rules:
MAX_EFFECTIVE_BALANCE
), extra amounts are to be skimmed (partially withdrawn) to withdrawal credentials
address (if 0x01
type is used)withdrawals credentials
(if 0x01
)Withdrawals represented via special operations are included into a block execution payload.
Thus, tracking the withdrawn funds when TVL updates is essential, especially considering consequent oracle reports.
As a simple example, suppose there is only one validator (v1
) and no buffered/transient ether:
stETH total supply
= v1 balance
+ withdrawals vault balance
The simplified single Lido validator lifecycle case is presented below (comparing two Ethereum network underlying mechanics in action: pre-/post- withdrawals):
Design points to update TVL calculation:
beacon balance
and withdrawals vault balance
at the same (i.e., synchronized) point of time to track validators' balances updates and prevent double-accounting. It is a crucial point of the described withdrawals-enabled accounting approach due to the asynchronous and autonomous nature of withdrawals on Ethereum: the Lido protocol can't use the balance of withdrawals vault
at the arbitrary moment because it's coupled with beacon balance
(which is inaccessible from the Execution Layer side without oracle).withdrawals vault balance
and appends the funds to buffered balance
, than withdrawals vault balance
can be safely omitted in the TVL formula (similar to ExecutionLayerRewardsVault
approach).stETH
withdrawal fulfillmentWithdrawal requests get accumulated via WithdrawalQueue
by locking stETH
on balance.
Then, the following actions are performed as part of the Oracle report:
buffered balance
withdrawals vault balance
to buffered balance
buffered balance
to WithdrawalsQueue balance
stETH
shares for the fulfilled withdrawal requestsThe last three steps lower TVL since WithdrawalQueue balance
is not included in the stETH
total supply. However, by shares burning, user balances get preserved via stETH
rebasing mechanics.
The scheme below outlines the Lido on Ethereum protocol design with enabled withdrawals.
The following appendixes provide additional details on rewards and commissions.
As part of each oracle report, the protocol needs to calculate rewards and distribute comissions (protocol fee). Rewards calculation happens inside Lido.handleOracleReport()
.
Pre-merge rewards were defined as:
appeared validators = (beacon validators new - beacon validators old)
rewards = (beacon balance new - beacon balance old) - (appeared validators x 32 ETH)
An illustrative example is provided below:
A Merge-ready version of the rewards estimation:
rewards = (beacon balance new - beacon balance old) - (appeared validators x 32 ETH)
+ withdrawn from execution layer rewards vault
The updated example:
rewards = (beacon balance new - beacon balance old) - (appeared validators x 32 ETH)
+ withdrawn from execution layer rewards vault
+ withdrawn from withdrawals vault
NOTE:
withdrawn from withdrawals vault
MUST rely onwithdrawals vault balance
reported by Oracle (i.e., synchronized withbeacon balance
)
Comission distribution happens by minting additional shares, see distributeFee
Fee distribution executes only if the beacon chain rewards (without the execution layer rewards) are positive. This criterion was introduced with the merge-ready Lido protocol upgrade and encourages validators to converge to a healhy state of profitable validation.
if rewards - withdrawn from execution layer rewards vault > 0 {
distributeFee(rewards)
}
Lido protocol mechanics (TVL calculation, staker balances tracking, withdrawals fulfillment) rely on the data, provided by the off-chain oracle running committee. To lower the attack surface, the protocol has on-chain security/sanity checks to prevent reporting malicious accounting data.
Another known risk is that data can be correct itself, but the state transition is too drastic, which encourages short-term arbs lowering the rewards of the long-term protocol holders. For instance, stETH
rebase must be limited to prevent oracle reports sandwiching.
Terms:
cap
โ hard restriction, oracle tx reverts if the cap value exceededlimiter
โ allows transferring up to the saturation amount, doesn't impose revertsCurrent protocol limits:
if (postReportTVL > preReportTVL)
revert if ((postReportTVL - preReportTVL) > APR relative annual cap)
else
revert if ((preReportTVL - postReportTVL) > APR relative per-report cap)
It should be possible to restrict APR by:
if (newBeaconBalance > oldBeaconBalance)
revert if ((newBeaconBalance - oldBeaconBalance) > APR relative annual cap)
else
revert if ((oldBeaconBalance - newBeaconBalance) > APR relative per-report cap)
NOTE: The cap can't be applied for
withdrawals vault
balance to prevent protocol blocking when someone sends a massive ether amount.
The unified limiter should follow these steps:
Additional caps:
NB: Amount of activations/exits scales with the amount of active validators and the limit is the active validator set divided by 64.000.
Until 327680 active validators in the network, 4 validators can be activated per epoch.
For every 65536 (=4 * 16384) active validator, the validator activation rate goes up by one.
The exactly same churn limit is applicable for exiting validators.10 validators per epoch requires 655360 active validators which translates to 2200 validators per day.
There are 487656 active Ethereum validators at the moment of writing (7 validators per epoch).