--- tags: LIP, Oracle, rebase, sanity check status: draft author: Alexey Potapkin, Greg Shestakov, Eugene Pshenichnyi --- # LIP-23: negative rebase sanity check with second opinion ## Abstract Improve the safety check for the accounting report in the case of a negative rebase, reducing the possible impact size, but with the requirement for a second opinion for extreme cases. ## Motivation The `AccountingOracle` contract is a fundamental component of the Lido protocol that delivers, amongst other data, the aggregate of all Lido validators' Beacon Chain balances (`clBalance` in the report) to the protocol, thereby facilitating the daily rebase of the stETH token. The accuracy of this value is critical to the integrity of the protocol, and it relies on a committee of independently operated Oracle daemons in a 5-of-9 configuration. The protocol could be harmed if this committee is compromised, malfunctions, or colludes. This risk is acknowledged and constrained by a sanity check that restricts the possible discrepancy in balance that Oracle can report. The current approach to sanity checking allows the Oracle committee to bring up to a 5% reduction of TVL in each report. Given that the governance reaction time has a lower bound of 72 hours, the malicious or compromised Oracles could reduce the TVL by 15-20%, invoking mass liquidations on lending markets and dropping the price of stETH. However, a real negative rebase has very distinct features that we can take into account to reduce the impact and attack surface while allowing frictionless operation of the protocol even during a mass slashing event. While a high-precision check is possible using zero-knowledge technologies and EIP-4788, it's rather complex approach and takes time to implement, review, audit, and establish a maintenance process. So, the solution that limits the impact in a simple but effective way could be employed in a short term while still having the possibility to add a more robust one later. ## Specification The proposed sanity check operates on the rebase value of the report $$ clRebaseValue_i = clBalance_i + withdrawalVaultBalance_i - clBalance_{i-1} - clDepositsAppeared_i $$ where - $clBalance_i$ — the aggregate balance of all Lido validators on Beacon Chain for the $i$th report day - $clDepositsAppeared_i$ — ether that was deposited to Beacon Chain appeared as new validators in $i$th report day - $withdrawalVaultBalance_i$ — the amount of ether that was withdrawn from the Beacon Chain to the Lido withdrawal vault on the moment of $i$th report day and $$ negativeClRebaseSum_{18} = \sum_{k=0}^{17} \begin{cases} |clRebaseValue_{i - k}| &, if\ clRebaseValue_{i - k} < 0 \\ 0 &, if\ clRebaseValue_{i - k} \geq 0 \end{cases} $$ In a way, that if $clRebaseSumNegative_{18} > maxClRebaseNegativeSum_{18}$ the check MUST try to retreive a second opinion on the report values. The check is considered failed if no second opinion is available or the second opinion does not match the report. $$ maxClRebaseNegativeSum_{18} = maxInitialSlashings_{18} + maxPenalties_{18} $$ $$ maxInitialSlashings_{18} = 1 ETH * (clValidators_i - clValidatorsExited_{i-18}) $$ $$ maxPenalties_{18} = 0.101 ETH * (clValidators_i - clValidatorsExited_{i-54})$$ where $clValidators_i$ — cumulative number of ever appeared Lido validators on Beacon Chain on the $i$th report $clValidatorsExited_i$ - cumulative number of Lido validators ever exited from Beacon Chain on the $i$th report >NOTE 1: In the case if $i-18$ or $i-54$ report is missing, it's safe to use the closest earlier report to construct $maxClRebaseNegativeSum_{18}$. >NOTE 2: During the bootstrap period, while historical data is not available, it's recommended to use $maxInitialSlashings_{18} = 1 ETH * clValidators_i$ until $i-18$th report is available, and $maxPenalties_{18} = 0.101 ETH * clValidators_i$ until ${i-54}$th report is available. ### Second opinion To start, it is proposed that there be no second opinion source but the interface to plug the desired provider later. The desired second opinion provider would be a ZKP-based oracle, like the ones proposed on the forum once they are ready: - [[ZKLLVM] Trustless ZK-proof TVL oracle](https://research.lido.fi/t/zkllvm-trustless-zk-proof-tvl-oracle/5028) - [DendrETH: A trustless oracle for liquid staking protocols](https://research.lido.fi/t/dendreth-a-trustless-oracle-for-liquid-staking-protocols/5136) - [ZK Lido Oracle powered by Succinct](https://research.lido.fi/t/zk-lido-oracle-powered-by-succinct/5747) Other options, like 3rd party Oracle comittee or even a multisig-controlled manual quasi-oracle, may be considered. The interface these Oracles should implement is defined as: ``` interface SecondOpinionOracle { function getReport(uint256 refSlot) external view returns ( bool success, uint256 clBalanceGwei, uint256 numValidators, uint256 exitedValidators ); } ``` > NOTE: The interface is excessive and provides more data than is used in this check. It was intended to provide a possibility for the more extensive use of ZK-based data in future checks. ### Matching As we're considering ZKP-based oracles as a baseline for the second opinion provider, the inherent limitations should be considered. We know from the proposals on the forum that the inclusion criterion for a validator to be considered belonging to Lido differs from Lido Oracles and may result in an error, given that anyone can spawn a validator with Lido's withdrawal credentials. Hence, ZK-proved values can indicate only the upper limit at this stage. However, as soon as such an attack costs ether, we can assume that for `clBalance`, there is a lower boundary defined by the economic viability of the attack. So, there MUST be a parameter for the sanity check, `clBalanceErrorMargin`, that can define the error tolerance level for `clBalance` matching. ## Rationale ### Calculation window length For calculation window length, an 18-day window is proposed. Such value is derived from Ethereum specification and ensures that any midterm penalties (if they are low enough) will be resolved in separate 18-day windows in case of sequential slashings. In reality, the window length should be 4096 epochs, but due to Oracle's daily report nature, this amount of epochs transformed into the closest amount of days (4096/225=18.2) For attestation penalties, we should consider exits which happened less than 54 days ago, due to slashing mechanism. Take a look at the picture: ![](https://hackmd.io/_uploads/BJH7kQgfR.png) For day X, the start of the window will be on day X-18. Consider the validator which was slashed 36 days before the start of the window (X-54). Such a validator would still generate attestation penalties which should be accounted for within an 18-day window. We take 36 days since the slashing vector is 36.4 days (8192 epochs), so this approach has no off-by-one error. Strictly saying, the complete formula should account for 1 day of penalties for the validator slashed 36 days before the start of the window, 2 days of penalties for the validator slashed 35 days before the start of the window, etc. Still, due to the complexity of such a calculation, it is decided to take 18 days of penalties (0.101 ETH) for every validator, to increase the safety margin and reduce the chances of false positive sanity check trigger. ### Maximum negative rebase value Let’s identify all possible sources of negative rebase, which could happen “naturally” (i.e., happen only because of the way Ethereum is designed and does not require malicious actions) That could be: - Attestation penalties (source and target) - Sync committee penalties - Initial slashing penalty  - Correlated slashing penalty - Inactivity leak penalty The overall table for 1 validator looks like this: | Source | Clause | Max decrease in 18 days, ETH | | -------------------------------- | ------------------------------------------ | ----------------------- | | Penalties (Attestation and sync) |Validator are off for 18 days, >100000 vals | 0.101 | | Initial slashing | Validator slashed | 1 | | Correlated slashing | More than 31% of network slashed | 31 | | Inactivity leak | Inactivity leak for 18 days | 12.6 | How numbers were calculated:  *Attestation penalties and sync committee* - for every size of the active validator set, it is possible to calculate the expected APR ([more on this here](https://eth2book.info/capella/part2/incentives/issuance/#validator-rewards)). Knowing the share of target+source penalty (40/64), it is possible to find that for 100 000 validators set, 1 validator would lose 0.3% or 0.1 ETH of its balance for 18 days of inactivity (and the more validator set would be, the less the decrease, for example for the current 1 million validators that number would be 0.1% or 0.03 ETH). We take max attestation penalties for 10000 validators set, to ensure that theoretical max attestation penalty values would work in all reasonable situations. Attestation penalties do not scale with participation, meaning if all Lido validators were inactive for 18 days, the overall $clBalance$ decrease would equal 0.1 ETH * $clActiveValidators$ . But there is another source of negative rebase - sync committee. To include the impact of the sync committee, we added the sync committee weight to the initial calculation, so the fraction is 42/64. As a result, the total amount of lost balance slightly increased to 0.32%. In absolute value, the maximal amount of penalties for attestations and sync committee is equal to 32*0.32=0.101 ETH per validator. The way of calculating the sync committee impact mentioned above is not strictly correct cause such an approach assumes that for 18 days, every validator from the Lido validator set would participate in the sync committee, which is impossible since the sync committee contains only 512 validators. However, this conservative approach would cover every possible share of Lido validators participating in the sync committee. ![](https://lh7-us.googleusercontent.com/bgVAAX-4DxMcVNC-Up79oZAbf0lbtLGYWIGFv6tCmzqhB_QuM1shUcY8SY5gjQf2n_KxlzvBeg4NGf8kw_jbeDTfwdyvFDtUIPA0_zWmGMgpoCpvZiP-qB7Cl-s6IPLYrHxHwuUyBW5NEwRQ1Fq6Ugk) *Initial slashing penalty* - initial slashing penalty equal to 1/32 of effective balance. For validator with 32 effective balance it is equal to 1 ETH. *Correlated slashing penalty* - increase by 1 ETH per approximately 1% of slashed active validator set. If 32% of the whole network is under slashing (including the whole Lido validator set), the inactivity leak is not triggered, but a correlated slashing penalty would be equal to 31 ETH (since 1 ETH was already loosed due to the initial slashing penalty). *Inactivity leak* - during an inactivity leak, the validator loses balance according to its inactivity points, which decreases with any missed attestation during an inactivity leak until the validator's effective balance reaches 16 ETH (with such balance exit triggers automatically). It would take 4686 epochs to reach a 16.75 ETH real balance, but as we use an 18-day window (or 4096 epochs), the overall decrease would be less than 16 ETH and equal to 12.6 ETH. ![](https://lh7-us.googleusercontent.com/zXMpEqZbDqki9xB2_Ez049GDXRPvtZDmEfF-KEHB_x9SHYydTDh5PveUkgS4QQB4Y5ZJbE6lE-uXCBzp9wTepR_xY-QhXyo4ZX1YhGSzFwodlX3QJDJnliU5mwqslwLWHZiKnk5N0SNR_aiQfJKOOO4) #### New sanity check value  Considering numbers from the previous chapter, it is proposed that the maximal decrease of the 18-day window sanity check for attestation penalties equal to 0.101 ETH and for initial slashing to 1 ETH. This means that even if all Lido validators would be slashed and inactive for 18 days, the sanity check still won’t be triggered. Such value covers a combination of 2 cases of ‘natural’ negative rebase.  As for the inactivity leak and correlated slashing penalty case, it is proposed that a second opinion be used to resolve such an issue. Scenarios with such a massive correlate penalty or inactivity leak are possible in the case of: - Bug in software, which leads to slashing - Validator infrastructure has been down for 18 days, without validators transferring to other providers To be more precise, events that are not covered by sanity check could happen either if there is critical bug in one of 4 most popular [consensus/execution clients](https://explorer.rated.network/network?network=mainnet&timeWindow=1d&rewardsMetric=average&geoDistType=all&hostDistType=all&soloProDist=stake)(Prysm, Geth, Nethermind, Lighthouse) or 6 top cloud service providers would go down for 4 days (after such time there will be more than 1.101 ETH decrease per each validator due to inactivity leak). Knowing that by now, there wasn’t a correlated slashing penalty higher than 0 (max amount of simultaneous slashing [within 36 days window is 107](https://www.chainproof.co/blog/three-years-of-ethereum-staking-a-review-of-slashing-events-and-other)), and there was only 1 inactivity leak, [which lasts 9 epochs](https://medium.com/offchainlabs/post-mortem-report-ethereum-mainnet-finality-05-11-2023-95e271dfd8b2). We assume, that it is reasonable to consider such events as extremely rare, and use second opinion to resolve such events. ## Security consideration ### Dual Governance clashing Considering the current design of the [Dual governance](https://research.lido.fi/t/dual-governance-design-and-implementation-proposal/7131), it is possible that the protocol will face a deadlock. If the protocol is in a rage quit state, votes cannot be executed unless all stETH is withdrawn from the rage quit contract. Withdrawal requires an Oracle report, so if something (for example, extremely high midterm slashing penalties) would cause the trigger of the sanity check, the protocol enters a dead-lock situation, where stETH can not be withdrawn due to the lack of Oracle reports, Oracle reports do not pass due to sanity check, and the sanity check cannot be changed since votes are blocked by Dual Governance veto. ### No second opinion in the beginning Possible risks are divided in a way that negative events that can happen swiftly fit under the limits and happen freely without triggering this sanity check, but larger ones, being extremely rare, can be predicted and give DAO time to react: 18 days in case of large correlated slashing and, at least, 4 days — in the case of the mass inactivity leak. So, starting without a second opinion may be considered safe until Dual Governance is implemented. ### Missed reports In case Oracle reports are missed for some reason, the calculation window may be increased (for example 3 days of missed reports will one-time add 3 days to the calculation window). Since the proposed design takes into account cumulative validators set, such an event may trigger a false positive sanity check, in case all validators are slashed on day x-18, and reports are missed on day x-1,x,x+1 (while no new validators are being added for at least 36 days before such scenario). In this case, the amount of accumulated attestation penalties would be higher than 0.101 ETH per validator, but since this amount of penalties is calculated for a validator set of 100000 validators, for a bigger set such a scenario wouldn't trigger a false positive sanity check. Once a trustless second opinion is added, this consideration will be fully mitigated. ### clValidators forging In this sanity check we rely on $clValidators$ value that Oracle brings with each report. But if oracle committee is colluded they can bring any values in this field. Therefore, it's not an issue, because $clValidators$ is capped by the number of validators deposited by Lido protocol, which is fixed onchain and their lower bound is the previous report value. So, it's not exploitable from this side. ### MVI changes Values in this doc are calculated considering current Ethereum issuance and vote weights; in the case of MVI implementation, values should be recalculated to reflect changes. ## Backward compatibility There is no need for any changes in the Oracle daemon. It MUST work as intended without modification and explicit knowledge about additional sanity checks. It will be able to utilize fastlane mechanics and reach a consensus but will fail to submit report data in the case of a significant decrease until the second opinion is ready. But it will retry until it finally succeeds. However, it can be optimized to avoid this polling loop and reduce resource utilization. Also, proposed approach does not require any additional data from Beacon Chain, so oracles are not to be modified. ## Links - [Accounting oracle specification](https://docs.lido.fi/guides/oracle-spec/accounting-oracle) - [Lido oracle report handling](https://docs.lido.fi/contracts/lido#oracle-report) - [[ZKLLVM] Trustless ZK-proof TVL oracle](https://research.lido.fi/t/zkllvm-trustless-zk-proof-tvl-oracle/5028) - [DendrETH: A trustless oracle for liquid staking protocols](https://research.lido.fi/t/dendreth-a-trustless-oracle-for-liquid-staking-protocols/5136) - [ZK Lido Oracle powered by Succinct](https://research.lido.fi/t/zk-lido-oracle-powered-by-succinct/5747)