lido
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Help
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    --- title: Withdrawals spec tags: withdrawals, lip, spec, design status: Pre-Draft author: Eugene Mamin, Alexey Potapkin, Artyom Veremeenko discussions-to: TBA created: 2022-09-13 updated: 2022-11-24 --- # Withdrawals spec >TODO [dzhon]: >- WithdrawalQueue as interface for users (minimize Lido bytecode size) >- IMPROVE exit signalling (separate contract is expected) >- naming >- oracle >- spin-off doc for discussion (DoF extracts) >- self-coverage moved to pre-rebase (?): pre-rebase / post-rebase cover | non-cover >- Versioning for initialization (LIP-10) >- Automated counters for exits to prevent NOs oversubmitting >- pre-rebase / post-rebase oracle report >- oracle invariants on expected ids >- finalization price needs to be described once again >- how to select finalization id (Oracle report flow) >- can have a separate contract to query for APR (to transplate the Oracle) >- Oracle may go not through Lido, but call separate contracts and Lido as the last one (idealistically) >- EIP-2612 >- setELRewardsVault + setProtocolContracts >- petrify >- stake limit constant gas > Frontend requests: > - Permit > - Claim multiple requests > - WstETH unwrap > - Show what Lido oracle would have been reported now upfront > Tooling > - Bootstrap non-exited ## Abstract A major Lido protocol upgrade that allows holders to redeem `stETH` back to `ETH` after the [`Shanghai`](https://ethereum-magicians.org/t/shanghai-core-eip-consideration/10777)/[`Capella`](https://github.com/ethereum/consensus-specs/issues/2758) hardfork. It consists of three relatively separate flows described further in the doc. ## General overview `stETH` holder interacts with Lido by locking tokens on [`WithdrawalQueue`](https://etherscan.io/address/0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f) and receiving an enqueued position index in return. The position is monotonically increasing counter representing the place in the internal withdrawal requests queue. Locked `stETH` continues accumulating rewards on behalf of other protocol users, the original holder stops receiving them since the block number of the successfully placed withdrawal request. Upon the next oracle report, previously accumulated withdrawal requests are processed and finalized if eligible (timelock passed and enough funds to fulfill). Other requests are carried over to the next oracle report round. `stETH` [shares](https://docs.lido.fi/guides/steth-integration-guide#steth-internals-share-mechanics) are burned on behalf of the Oracle report together with decreasing the total pooled ether amount. The logic of withdrawal requests unwinding and processing is implemented within the off-chain Oracle daemon. The Oracle daemon provides the following data during the reports: the index of the last finalized withdrawal request, total Lido validators balance, active validators number, exited validators number per each node operator, balance of the Lido withdrawal credentials address (all info is valid for the last block of the expected report epoch: [[1]](https://docs.lido.fi/contracts/lido-oracle#getexpectedepochid), [[2]](https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-2.md#use-only-the-first-epoch-of-the-current-frame-for-oracles-reporting)). The oracle must track all of the Lido-participating validators' exits (node operators could also initiate a voluntary exit without request from Lido) that occurred over the previous oracle round and modify their on-chain state stored within Lido protocol to process rewards and balance the newly submitted ether. After the withdrawals requests have been processed and finalized, anyone can trigger sending the claimed ether by providing finalized position index to the original requestor (recipient). Another signalling off-chain process which has its own lifecycle, is needed to report the validator keys needs to be exited to fulfill withdrawal requests. This process could be executed on behalf of the main Oracle committee to request the exit of specific validators based on the amount of withdrawal requests, slashing conditions, status/performance of validators controlled by Lido, staked ether buffer size, execution layer rewards, and previously requested withdrawals. The order of the proposed exits is deterministic and verifiable externally (i.e., sorted by validator's index resembling validator age counted from its activation). >NOTE: >Withdrawals design landscape id described [here](https://hackmd.io/tbBm1oIiQtCLbDDmYIAExw?view) in depth (WIP). #### Slashing If Lido validator(s) got slashed, then withdrawal requests placed since the day of slashing will be finalized no earlier then midterm slashing penalties would have been taken into accound (~18 days). New overlapping slashings don't affect the withdrawal request fulfillment to prevent an obvious denial of service attack vector (by slashing single validator each ~18 days). The diagram below outlines the idea of slashing delays: ```mermaid gantt title Processing requests with undergoing slashing axisFormat %b %d section Slashings Slashing 1 :a1, 2022-10-11, 36d Slashing 1 midterm : milestone, m1, 2022-10-29, Slashing 2 :a2, 2022-10-16, 36d Slashing 2 midterm : milestone, m2, 2022-11-03, Slashing 3 :a3, 2022-11-18, 36d Slashing 3 midterm : milestone, m3, 2022-12-06, section Withdrawal requests Request 1 (no extra delay) :r1, 2022-10-08, 2d Request 2 (+18d of slashing 1):r2, 2022-10-11, 18d Request 3 (+15d of slashing 1):r3, 2022-10-14, 15d Request 4 (+10d of slashing 2):r4, 2022-10-24, 10d Request 5 (no extra delay):r5, 2022-11-15, 2d Request 6 (no extra delay):r6, 2022-12-07, 2d ``` Further details are provided within the dedicated [Slasing conditions](#Slashing-conditions) section. #### Funding sources for withdrawals There are three possible funding sources to fulfill withdrawal requests: 1. Native post-Shanghai/Capella [Ethereum withdrawals](https://eips.ethereum.org/EIPS/eip-4895) - full withdrawals of exited validators - partial withdrawals or ["skimmed rewards"](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#introduction) 2. Execution Layer rewards (post-Merge block proposer rewards: [priority fees and MEV](https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-12.md)) 3. Deposit buffer (user-submitted ether in exchange of `stETH`) ##### Optimistic funding In most of the cases, Lido withdrawal requests could be fulfilled using all three sources. There is a reserve mark securing particular funds amount for withdrawals using the sources 2 and 3, allowing extra amounts be re-staked back into the protocol boosting capital efficiency. Reserve mark updates on each oracle report to address two considerations: - hold funds to partially fill first unfinalized withdrawal request (crucial if it's huge) - disable deterministic funds amount being used for deposits untill the next Oracle report (i.e., disentangle deposits flow) ##### Fallback to the native-only funding approach In case of newly registered since the previous report slashing events (*NB: even for non-Lido validators*), the Oracle is allowed to use only the first source to fulfill withdrawal requests on behalf of the current report. See [new slashings events](#New-slashing-events) for more details about buffers access. ## Flows ### Staker's flow Staker's flow describes all the steps that are required for a common user of the protocol to withdraw their ether. The overall staker's flow as a sequence diagram is represented below: ```mermaid %%{ init: { "theme": "forest" } }%% sequenceDiagram title Staker's flow participant Staker participant WithdrawalQueue participant Anyone Note right of Staker: Step 1 Staker->>WithdrawalQueue: requestWithdrawal(amount of stETH to lock) WithdrawalQueue-->>Staker: return (request id) Note right of Anyone: Step 2 loop Untill (request status) is final Anyone->>WithdrawalQueue: withdrawalRequestStatus(request id) WithdrawalQueue-->>Anyone: return (request status) end Note right of Anyone: Step 3 Anyone->>WithdrawalQueue: claimWithdrawal(request id) WithdrawalQueue->>Staker: transfer ether (#8804; originally locked stETH amount) ``` ##### Step 1. Initiate a request Withdrawal requests are initiated by a stETH holder via the `WithdrawalQueue` contract using the entry-point `requestWithdrawal` method which: - irreversibly locks given `stETH` amount by trasferring it from the caller (`msg.sender`) relying on provided allowance (explicit approval) - enqueues withdrawal request into the contract's requests queue - associates an incremental unique id (i.e., position in the FIFO-serving queue) with the caller's address The amount of ETH that staker will receive upon withdrawal is capped at this stage (locked `stETH` stops accrueing rewards on behalf of the caller since the moment of withdrawal request had been enqueued succesfully). ##### Step 2. Wait until the request finalization There are two approaches to track requests finalization: - On-chain: call the permissionless `withdrawalRequestStatus` method of the `WithdrawalQueue` contract allows tracking withdrawal requests providing the request id. - Off-chain: subscribe to the `WithdrawalsFinalized` events emitted by the contract with a terminating condition if last finalized unique id becomes equal or greater than the needed unique id. The withdrawal request recipient can receive in the end: - ether amount corresponding to the locked `stETH` amount when called the `requestWithdrawal` method previously - ether amount less than locked `stETH` amount in case of mass slashing events (unlikely, will be described further below) This amount becomes fully determined (immutable) once the withdrawal request marked as finalized and becomes claimable. Withdrawal requests are expected to become finalized in a couple of days for the most cases. In case of mass slashing events and penalties finalization needs to be postponed up to ~18 days. ##### Step 3. Claim ether Once withdrawal request has been fulfilled completely and marked as finalized, anyone is able to call the `claimWithdrawal` method on the `WithdrawalQueue` contract, providing the original unique withdrawal request id. The method transfers ethers to the original requestor (recipient) who locked their `stETH` on the first step. ### Oracle flow (protocol accounting) Rough oracle flow is the following: - **take a pre- snapshot of the `stETH` share price** - update exited validators number for each Lido-participating node operator - estimate transient validators balance - finalize withdrawal requests (if possible) by burning `stETH` shares and locking ether in the same time (i.e., decreasing a total pooled ether amount) - resubmit remaining funds up to the reported `WithdrawalQueue` balance at block `N` to the deposit buffer - set the reserve mark value to leave the necessary amount of funds in deposit buffer till the next report to use for withdrawals - **take a post- snapshot of the `stETH` share price for rewards** - calculate both consensus and execution layer rewards - mint&distribute protocol fee on top of rewards only if consensus layer rewards part is positive - **take a post- snapshot of the `stETH` share price for APR calculation** Oracle report should provide the following data: - Consensus layer data: * Lido validators balances sum at block `N` * Active Lido validators number at block `N` * Cumulative number of exited Lido validators number at block `N` * List of exited Lido validators number at block `N` per each Node Operator since the previously completed report - Execution layer data: * The balance of `WithdrawalQueue` available for distribution at block `N` - Decision data: * New withdrawals buffered ether reserve mark value * List of triples: [last finalized request id, finalized stETH amount, finalized shares amount] Requirements and constraints: - Block `N` corresponds to the last block of the **finalized** expected epoch - Off-chain Oracle daemon decides how many withdrawal requests have to be finalized on each report. It **must** suspend finalization if slashing occured until midterm penalty applied (see [Slashing](#Slashing) and [Slashing Conditions](#Slashing-conditions)) - List of triples `[last finalized request id, amount of finalized stETH, amount of finalized shares]` is used to deliver the values of the previously missed reportable epochs since the last successfully completed report - Oracle is able to finalize the requests happened only BEFORE the fixed offset from block `N` (10 epochs by default) - A validator is exited once became [inactive](https://github.com/ethereum/consensus-specs/blob/11a037fd9227e29ee809c9397b09f8cc3383a8c0/specs/phase0/beacon-chain.md#is_active_validator) (reached the assigned `exit_epoch`) - The Oracle daemon are unable to deliver the retrospective report if an [expected reportable epoch](https://docs.lido.fi/contracts/lido-oracle#getexpectedepochid) was missed. A new report should deliver the data for missed period(s) happened since the last successfully completed report. #### Momentary stETH total supply (TVL) Previously [total stETH supply](https://docs.lido.fi/contracts/lido#rebasing) (or `totalPooledEther`) for Lido was defined as follows: ```rust totalPooledEther = bufferedEther + transientBalance + beaconBalance' ``` where: * `bufferedEther` — ether stored on the Lido contract and haven't deposited to official Deposit contract yet (*momentary*) * `transientBalance = (depositedValidators - beaconValidators') * DEPOSIT_SIZE` — ether submitted to the official Deposit contract but not yet visible in the beacon state (*momentary*) * `beaconBalance'` — total amount of ether on validator account (reported by oracles, having strongest impact to stETH total supply) * `'` - is for values that corresponds to the latest oracle report (which already looks in the past) New formula for total supply (`totalPooledEtherNew`) is following: ```rust totalPooledEtherNew = bufferedEther + transientBalanceNew' + beaconBalance' ``` where: * `transientBalanceNew = (depositedValidators - beaconValidators' - withdrawnValidators') * DEPOSIT_SIZE` (*momentary*) The formula is valid during the whole lifecycle except the oracle-report quorum-reaching tx internals. #### Reward distribution run Previously rewards amount was calculated by the following formula: ```rust totalRewards = postTotalPooledEther - preTotalPooledEther = (postBeaconBalance - preBeaconBalance) - (preTransientBalance - postTransientBalance) + (postBufferedEther - preBufferedEther) ``` where: * `preTotalPooledEther` — total pooled ether amount, queried right BEFORE every report push to the Lido contract * `postTotalPooledEther` — total pooled ether amount, queried right AFTER every report push to the Lido contract * `(postBufferedEther - preBufferedEther) = executionLayerRewards` — amount of execution layer rewards collected between two consequitive oracle reports (became non-zero starting from the Merge) Protocol fee distribution was performed only if the following condition is met: `postBeaconBalance > preBeaconBalance` Pre-withdrawals rewards were from beacon chain balance increase and execution layer tips/MEV. Post-withdrawals rewards calculation should include the third source: burning `stETH` shares matching the previous 'total pooled ether' per 'single share' ratio. Effectively, it means that reward distribution should be dependant not only on `totalPooledEther`, but also on the underlying shares amount change: Now it should be updated to: ```rust preSharesRate = preTotalPooledEther / preTotalShares postSharesRate = postTotalPooledEther / postTotalSharesPreFee totalRewards = postTotalSharesPreFee * (postSharesRate - preSharesRate) ``` where: * `preTotalShares` — `stETH` shares amount, queried right BEFORE every report push to the Lido contract * `postTotalSharesPreFee` - `stETH` shares amount, queried right AFTER every report push to the Lido contract (before distributing protocol fee) Protocol fee mint&distribution should be performed only if the following condition is met: `(totalRewards - executionLayerRewards) > 0` >NOTE >In case of external token integrations rewards calculation should take into account > [In-protocol coverage application mechanism (LIP-6)](https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-6.md). #### APR An APR calculation should also be updated to the following formula: ```rust preSharesRate = preTotalPooledEther / preTotalShares postSharesRate = postTotalPooledEther / postTotalSharesPostFee APR = (postSharesRate - preSharesRate) * secondsInYear / (preSharesRate * timeElapsed) ``` where: * `postTotalSharesPostFee` - `stETH` shares amount, queried right AFTER every report push to the Lido contract (after distributing protocol fee) * `secondsInYear` — seconds amount per single year * `timeElapsed` — time passed since the previous oracle report (in seconds) >NOTE >The APR calculation is opaque to the self-coverage application and protocol fee amount. #### Withdrawal request finalization Pending withdrawal requests got finalized as a part of the Oracle report. The Oracle report delivers the following values used to finalize the withdrawal requests: * the balance of WC address at block N * the amount of locked ether on WC address at block N * the list of triples to describe each finalized batch of requests: * id of the last withdrawal request id in the batch * the total pooled ether amount per single stETH (i.e., share price) in the batch It means, that requests till last withdrawal request id from the list's tail element become finalized (or claimable), noting `id` of the last finalized withdrawal request at block `N` is required to avoid on-chain loops over the withdrawal requests on their finalization. The total pooled ether amount per single stETH share for the blocks range got recorded into the table to facilitate a calculation of ether amount claimable for each request (even for old ones). ```mermaid sequenceDiagram title Oracle flow participant OO as Off#hyphen;chain oracle participant WQ as WithdrawalQueue participant LO as LidoOracle participant Lido OO-->>OO: determine finalized block N OO->>OO: read CL data at block N OO->>OO: read WC balance at block N OO->>WQ: readLastRequestId(block N) WQ-->>OO: return (lastRequestId) loop Collect unfinalized requests info (≤ lastRequestId) OO->>WQ: getRequestInfo(requestId) WQ-->>OO: return (request info) end OO-->>OO: determine last requestId for finalization OO-->>LO: push CL, EL data + last requestId for finalization LO-->>Lido: update accounting Lido-->>WQ: finalize(lastRequestId, currentSharePrice) ``` ### Exit signals flow >Same committee and codebase as for the existing Oracle The exit daemon is used to inform that some Lido validators should be ejected. The purpose of the daemon is to secure and distribute these kind of reports via smart-contract events. It's architecture is akin [Generalized Message Bus](https://ethresear.ch/t/withdrawal-credentials-exits-based-on-a-generalized-message-bus/12516) approach introduced previously. Exit daemon runs its own lifecycle and doesn't need to be aligned with the general Lido oracle reports. Exit daemon delivers the following data: - an exact pubkey to exit - NO id and stakingModuleId where the pubkey belongs - validator index corresponding to the validator with the pubkey - block/epoch number of decision (Q: not needed?) - proof of validity (merkle root of all keys?) > TODO: Node Operator can stop responding at all, we want to account it in exit algo > Q: If store counters of non-exited (not responded in time with VoluntaryExit command) for every NO, how is it gointg *actually* be used? > Q: If validator responded > TODO: after oracle cold start: what data is needed to understand how far in past Oracle would need to go #### Calculate/evaluate amount of validators for exit Rough algorithm looks like this. 1. Take not finalized withdrawal requests form `WithdrawalQueue` 1. Evaluate amount of the requests which don't require to exit any additional validators: 1. Number of requests for which there is enough ether in buffers permitted for withdrawals 2. Number of requests which are going to receive their ether from pending exit requests (requested and for which the validators responded under `TIMEOUT_FOR_EXIT_REQUEST`) 3. /optional/ Take into account the ether which is going to come from sponteneous validator exits 1. Calc amount of packs of 32 ETH needed to satisfy ether demand from the rest of the withdrawal requests #### Select specific validators for exit Based on the [exit order discussion](https://research.lido.fi/t/withdrawals-on-validator-exiting-order/3048). 1. Take list of Lido validators ordered by validator activation time ascending 2. Identify the last validator requested for exit (by taking the last `ValidatorExitRequest` event) 3. Take the first `N` validators, skipping already exited / exiting validators #### Parameters of protocol exit flow Parameters of the protocol related to validator exit flow - `TIMEOUT_FOR_EXIT_REQUEST` Time since emitting `ValidatorExitRequest` event till the corresponding validator is considered non-responded and skipped. Likely should be stored in `ValidatorExitBus` contract - `MAX_KEYS_TO_REPORT_FOR_EXIT_AT_ONCE` Constant to prevent oracle from hitting block gas limit while reporting to `ValidatorExitBus`. Determined based on the transaction gas cost - `MAX_EXIT_REQUESTS_PER_DAY` Safely limit on the amount of exit reports allowed per day. To protect from exiting insane amounts of validators due to malicios or incorrect oracle reports. ```mermaid %%{ init: { "theme": "neutral" } }%% sequenceDiagram title Exit daemon flow participant ED as Exit daemon participant LO as Lido Oracle participant NO as Node Operator ED-->ED: determine amount of validators keys to exit loop Push validators keys (≤ keys count) ED-->LO: push next validator pubkey LO-->LO: emit event with validator pubkey end loop Waiting for relevant keys NO-->LO: read events for relevant pubkey NO-->NO: sign exit message and send to CL if relevant end ``` * The propose tooling for signing exit messages described [here](https://research.lido.fi/t/withdrawals-automating-lido-validator-exits/3272) ## Technical Scope ### On-chain smart contracts #### Lido An facade for users to stake and unstake (withdraw) the funds, and StETH manager as well. - stETH ERC-20 logic - stakers top-level interactions (submit/withdraw entry point) - protocol parameters management - rewards/fee distribution #### WithdrawalQueue A separate smart contract that allows to lock up stETH to get the position marker in return, and once the protocol could burn stETH and return ETH (*replaces the [Withdrawal Manager Stub](https://docs.lido.fi/contracts/withdrawals-manager-stub) by occupying the [WC address](https://etherscan.io/address/0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f). #### OracleBase Base contract for both `LidoOracle` and `ValidatorsExitBusOracle` with a commong logic - Commitee and quorum logic - Reports merkalization and housekeeping (if needed) - General-purpose raw-encoded calldata collecting #### LidoOracle (derived from OracleBase) - Validators statuses for the expected epoch - WC balances for the expected epoch - Sanity checks - stETH rebase (updates TVL, initiates reward distribution run - Pre- and post- rebasing hooks - Reports calldata has a typed interface #### ValidatorsExitBusOracle (derived from OracleBase) - Next batch for exit reports by oracle committee - Sanity checks - Rate limits for exiting keys - Events to inform external observes regarding each key - Reports calldata has a typed interface ## Smart contracts specification ### Lido.sol >NOTE: >The following definitions presume the Solidity v0.4 syntax. The following new entities are defined: #### Functions ##### Function: `requestWithdrawal` ```solidity function requestWithdrawal( uint256 _amountOfStETH ) external returns (uint256 requestId); ``` Irreversibly locks `_amountOfStETH` stETH to place the withdrawal request from `msg.sender`. Transfers `_amountOfStETH` to the `WithdrawalQueue` contract. Returns the incremental unique enqueued request position (`requestId`). - reverts if `msg.sender` have stETH balance lower than `_amountOfStETH` - reverts if `_amountOfStETH` lower than `MIN_STETH_WITHDRAWAL_AMOUNT` - reverts if `_amountOfStETH` greater than `MAX_STETH_WITHDRAWA_AMOUNT` - reverts if withdrawals are paused - reverts if protocol is paused - emits `WithdrawalRequested(_requestId, msg.sender, _amountOfStETH, StETH.getSharesByPooledEther(_amountOfStETH))`. >NOTE: > `WithdrawalQueue.MIN_STETH_WITHDRAWAL_AMOUNT = 0.1 stETH` by default > `WithdrawalQueue.MAX_STETH_WITHDRAWA_AMOUNT = 16000 stETH` by default ###### Parameters | Name | Type | Description | | ---------------- | --------- | ------------------------------- | | `_amountOfStETH` | `uint256` | stETH amount to withdraw as ETH | ##### Functon: `withdrawalRequestStatus` ```solidity function withdrawalRequestStatus(uint256 _requestId) external view returns ( address recipient, uint256 requestBlockNumber, uint256 etherToWithdraw, bool isFinalized, bool isClaimed ) ``` Retrieves the status of the previously placed withdrawal request. Returns the exhaustive info about the request status. - returns all zero fields if `_requestId` is invalid (not placed yet) - never reverts intentionally ###### Parameters | Name | Type | Description | | ----------- | --------- | ---------------------------------------------------------------- | | `_requestId`| `uint256` | withdrawal request id returned by `requestWithdrawal` previously | ##### Function: `claimWithdrawal` ```solidity function claimWithdrawal(uint256 _requestId) external; ``` Transfers withdrawn ether back to the `_requestId` recipient. Emits `WithdrawalClaimed(_requestId, recipient, msg.sender)`. - reverts if `_requestId` is invalid or already claimed - reverts if `withdrawalRequestStatus(_requestId)` returns `isFinalized == false` ###### Parameters | Name | Type | Description | | ----------- | --------- | ---------------------------------------------------------------- | | `_requestId`| `uint256` | withdrawal request id returned by `requestWithdrawal` previously | ##### Function: `handleOracleReport` ```solidity function handleOracleReport( // CL values uint256 _beaconValidators, uint256 _beaconBalance, uint256 _totalExitedValidators, uint256[] _stakingModuleIds, uint256[] _nodeOperatorsWithExitedValidators, uint256[] _exitedValidatorsNumbers, // EL values uint256 _wcBufferedEther, // decision uint256 _newDepositBufferWithdrawalsReserve, uint256[] _requestIdToFinalizeUpTo, uint256[] _finalizationPooledEtherAmount, uint256[] _finalizationSharesAmount ) external; ``` Updates the protocol's state depending on the oracle-provided data. Reports finalized withdrawals requests. - reverts if sanity checks were not met (TODO) - reverts if called by anyone except [`LidoOracle`](https://docs.lido.fi/contracts/lido-oracle) - reverts if `_beaconValidators + _totalExitedValidators` is greater than ever deposited Lido validators - emits ... TODO ###### Parameters | Name | Type | Description | | ------------------------------------- | ----------- | -------------------------------------------------- | | `_beaconValidators` | `uint256` | number of Lido's keys in the beacon state | | `_beaconBalance` | `uint256` | summarized balance of Lido-controlled keys in wei | | `_totalExitedValidators` | `uint256` | aggregated number of Lido's ever exited validators | | `_stakingModuleIds` | `uint256[]` | node operators modules indexes | | `_nodeOperatorsWithExitedValidators` | `uint256[]` | node operators with exited validators (indexes) | | `_exitedValidatorsNumber` | `uint256[]` | number of Lido's exited validators in the beacon state for each node operator (see above) | | `_wcBufferedEther` | `uint256` | buffered withdrawal credentials balance | | `_newWithdrawalsReservedAmount` | `uint256` | deposit buffer and EL rewards aggregated amount reserved for withdrawals till the next Oracle report | | `_requestIdToFinalizeUpTo` | `uint256[]` | array of the request ids to finalize up to | | `_finalizationSharePrice` | `uint256[]` | array of the request finalization stETH share prices | #### Events ##### Event: `WithdrawalRequested` ```solidity event WithdrawalRequested( uint256 indexed requestId, address indexed recipient, uint256 amountOfStETH, uint256 amountOfShares, ); ``` Emitted when a new withdrawal request placed. See: `requestWithdrawal`. ##### Event: `WithdrawalClaimed` ```solidity event WithdrawalClaimed( uint256 indexed requestId, address indexed recipient, address initiator ); ``` Emitted when withdrawal request claimed and ether transferred to `recipient`. See: `claimWithdrawal`. ### WithdrawalQueue #### Public constants (immutables) ##### Constant: MIN_STETH_WITHDRAWAL_AMOUNT ```solidity uint256 public constant MIN_STETH_WITHDRAWAL_AMOUNT = 0.1 ether; ``` Minimum stETH amount to request a withdrawal. Used to prevent unbearable gas costs and primitive grieffing. ##### Constant: MIN_FINALIZATION_BLOCKS ```solidity uint256 public constant MIN_FINALIZATION_BLOCKS = 10 * 32; /* 1 hour */ ``` Minimum blocks number to finalize the withdrawal request. ##### Constant: MAX_STETH_WITHDRAWAL_AMOUNT ```solidity uint256 public constant MAX_STETH_WITHDRAWAL_AMOUNT = 500 * 32 ether; /* 16 000 stETH */ ``` Maximum stETH amount to request a withdrawal. Used to prevent long hardly fillable requests ##### Immutable: LIDO ```solidity address public immutable LIDO; ``` An explicit pointer to the `Lido` contract used for auth purposes. #### Structs ##### Struct: Request ```solidity struct Request { address recipient; uint96 requestBlockNumber; uint128 etherAmount; uint128 sharesAmount; bool claimed; } ``` The structure is used to represent the withdrawal request in the Lido withdrawals queue. ##### Struct: Request #### Functions ##### Constructor ```solidity constructor(address _owner); ``` Constructs the `WithdrawalQueue` instance by setting the `_lido` address. - reverts if `_owner` is zero address - reverts if `_owner` is not a contract ###### Parameters | Name | Type | Description | | ------- | --------- | --------------------------------------- | | `_lido` | `address` | address of the deployed `Lido` contract | ##### Function: `enqueue` ```solidity function enqueue( address _recipient, uint256 _etherAmount, uint256 _sharesAmount ) external returns (uint256 requestId); ``` Place a request in a queue. Returns an incremental unique position id within the queue. - reverts if `msg.sender` is not `LIDO` ###### Parameters | Name | Type | Description | | ----------------- | --------- | ----------------------------------- | | `_recipient` | `address` | address of the withdrawal recipient | | `_etherAmount` | `uint256` | locked amount of stETH | | `_sharesAmount` | `uint256` | locked amount of shares | ##### Function: `finalize` ```solidity function finalize( uint256 _lastIdToFinalize, uint256 _totalPooledEther, uint256 _totalShares ) external payable returns (uint256 sharesToBurn) { ``` Marks next requests in the queue finalized up to `_lastIdToFinalize` index. Returns amount of shares to be burnt. - reverts if `msg.sender` is not `LIDO` (TODO: why not Oracle?) - reverts if `_lastIdToFinalize` is invalid - reverts if `_lastIdToFinalize` is less then reported before - reverts if the `_lastIdToFinalize` request was placed later than the `_finalizationBlack - MIN_FINALIZATION_BLOCKS` block - reverts if not enough funds are passed to fulfill the requests ###### Parameters | Name | Type | Description | | ------------------------- | --------- | -------------------------------| | `_lastIdToFinalize` | `uint256` | last id considered as finalized| | `_totalPooledEther` | `uint256` | | | `_totalShares` | `uint256` | | ##### Function: `claim` ```solidity function claim(uint256 _requestId) external returns (address recipient); ``` Transfers locked ether to the `recipient` address (original withdrawal recipient associated with the request). Permanently removes the `_requestId` request from the queue. Returns the original withdrawal request `recipient` address. ###### Parameters | Name | Type | Description | | ---------------- | --------- | ---------------------------| | `_requestId` | `uint256` | the withdrawal request id | #### Events ##### Event: `WithdrawalsFinalized` ```solidity event WithdrawalsFinalized( uint256 indexed prevFinalizedRequestId, uint256 indexed lastFinalizedRequestId, uint256 finalizedBlock, uint256 amountOfFinalizedShares, uint256 amountOfFinalizedEther ); ``` Emitted when a bunch of withdrawal requests finalized. See: `TODO`. ### LidoOracle #### Functions ##### Function: `setReportAlgorithmVersion` ```solidity function setReportAlgorithmVersion(uint256 _expectedVersion, uint256 _timestamp) external; ``` Sets expected Oracle algorithm version since the provided `timestamp`. - reverts if called by anyone except `DAO Agent` - reverts if `_timestamp < block.timestamp` (retroactive change) - emits `ReportAlgorithmUpdated(_expectedVersion, _timestamp)` ##### Function: `getCurrentReportAlgorithmVersion` ```solidity function getCurrentReportAlgorithmVersion() external view returns (uint256); ``` Retrieves the current Oracle algorithm version. ##### Function: `getExpectedReportAlgorithmVersion` ```solidity function getExpectedReportAlgorithmVersion() external view returns ( uint256 expectedVersion, uint256 timestamp ); ``` Retrieves the expected Oracle algorithm version. Returns `timestamp == 0` and `expectedVersion == getCurrentReportAlgorithmVersion()` if there is no an already assigned expected version. #### Events ##### Event: `ReportAlgorithmUpdated` ```solidity event ReportAlgorithmChanged( uint256 indexed expectedVersion, uint256 indexed activationTimestamp ); ``` ### ValidatorsExitBusOracle > Q: maybe separate `ValidatorsExitBus` and `ValidatorsExitBusOracle` where Oracle contains committe / batch reporting / merkalization logic and main contract bus logic. Or at least inherit one from another > The committe is updated by DAO, don't want to do two updates if the committe is actually the same. Is it possible to change LidoOracle address (if go crazy %)) #### Functions ##### Constructor ```solidity constructor(address _lidoOracle); ``` Initialize the instance with the provided `LidoOracle` address. ##### Function: `reportKeysToEject` ```solidity function reportKeysToExit( uint256[] calldata _stakingModuleIds, uint256[] calldata _nodeOperatorIds, bytes[] calldata _validatorPubkeys ) external; ``` Reports validators pubkeys to be exited next. - reverts if `msg.sender` is not in `lidoOracle.getOracleMembers()` - reverts if args arrays length doesn't match - reverts if args arrays are empty - emits `ValidatorExitRequested` (for each pubkey) ##### Function: `increaseUnexitedValidatorsNumber` ```solidity function increaseUnexitedValidators( uint256 _stakingModuleId, uint256 _nodeOperatorId, uint256 _incrementNumber ) external; ``` Reports validators pubkeys to be exited next. - reverts if `msg.sender` is not in `lidoOracle.getOracleMembers()` - reverts if ##### Function: `decreaseUnexitedValidators` ```solidity function decreaseUnexitedValidatorsNumber( uint256 _stakingModuleId, uint256 _nodeOperatorId, uint256 _decrementNumber ) ``` ##### Function: `setExitingValidatorsLimit` ```solidity function setExitingValidatorsLimit( uint256 _maxExitingValidatorsLimit, uint256 _exitingValidatorsIncreasePerBlock ); ``` Sets the exiting validators rate limit. - reverts if `_maxExitingValidatorsLimit` == 0 - reverts if `_maxExitingValidatorsLimit` >= 2^64 - reverts if `_maxExitingValidatorsLimit` < `_exitingValidatorsIncreasePerBlock` - reverts if `_maxExitingValidatorsLimit` / `_exitingValidatorsIncreasePerBlock` >= 2^32 (only if `_exitingValidatorsIncreasePerBlock` > 0) ##### Function: `getCurrentExitingValidatorsLimit` ```solidity function getCurrentExitingValidatorsLimit() external view returns (uint256); ``` Returns how much validators can be reported to exit in the current block. ##### Function: `getExitingValidatorsLimitFullInfo` ```solidity function getExitingValidatorsLimitFullInfo() external view returns ( uint256 currentExitingValidatorsLimit, uint256 maxExitingValidatorsLimit, uint256 prevExitingValidatorsLimit, uint256 prevExitingReportBlockNumber ); ``` Returns full info about current validator exits limit params and state. #### Events ##### Event: `ValidatorExitRequested` ```solidity event ValidatorExitRequested( uint256 indexed stakingModuleId; uint256 indexed nodeOperatorId; bytes validatorPubkey; ) ``` ##### Event: `UnexitedValidatorsChanged` ```solidity event UnexitedValidatorsChanged( uint256 indexed stakingModuleId; uint256 indexed nodeOperatorId; uint256 unexitedValidators; ) ``` ##### Event: `ExitingValidatorsLimitSet` ```solidity event ExitingValidatorsLimitSet( uint256 maxExitingValidatorsLimit, uint256 exitingValidatorsIncreasePerBlock ) ``` ## Slashing conditions ### Penalties socialization and accounting principles Consensus layer specifications define the following types of the [slashing penalties](https://docs.prylabs.network/docs/how-prysm-works/validator-lifecycle#slashing-state): - one-time minimum penalty when the misbehaving validator being caught - midterm attack multiplier penalty - missed attestation (inactivity) penalty on the beginning of each epoch until exited ```mermaid gantt title Ethereum slashing penalties timeline axisFormat %d section begin Slashing started (epoch0) : milestone, m3, 2022-10-01, section slashing Slashing duration :crit, :a1, 2022-10-01, 36d Minimum penalty (epoch0) : milestone, m1, 2022-10-01, Midterm Attack Multiplier Penalty (epoch0 + 2^12): milestone, m2, 2022-10-19, Missed attestation penalties (epoch0, ..., epoch0 + 2^13) :a2, 2022-10-01, 36d section end Slashing completed (epoch0 + 2^13) : milestone, m3, 2022-11-06, ``` Missed attestation penalties are significantly lower than previous two. The second penalty resolves only on the 2^12-th epoch from epoch of the detected slashing. To socialize penalties with both long-term stakers staying in pool and those who want to withdraw, the withdrawal requests finalization is delayed if slashing started in the same day of withdrawal request or before all ongoing slashings midterm penalties would have been resolved. These requests are prolonged up to 2^12 CL epochs at least to resolve the midterm attack multiplier penalty. Up-to-date post-Merge missed attestation penalties for slashed validators: ```python # for every `epoch` of missed attestation during the slashing EFFECTIVE_BALANCE_INCREMENT = 10**9 # 1 Gwei BASE_REWARD_FACTOR = 64 total_active_balance[epoch] = 15 * 10**6 * 10**9 # current estimation in Gwei base_reward_per_increment[epoch] = Gwei( EFFECTIVE_BALANCE_INCREMENT * BASE_REWARD_FACTOR // integer_squareroot(total_active_balance) ) increments[epoch] = effective_balance // EFFECTIVE_BALANCE_INCREMENT base_reward[epoch] = Gwei(increments[epoch] * base_reward_per_increment[epoch]) WEIGHT_DENOMINATOR = 64 weight = TIMELY_SOURCE_WEIGHT + TIMELY_TARGET_WEIGHT = 14 + 26 = 40 epoch_penalty[epoch] = Gwei(base_reward[epoch] * weight // WEIGHT_DENOMINATOR) ``` To allow finalizing withdrawal requests right after the midterm attack multiplier penalty applied, could be estimated as follows: ```python # start from the epoch of the applied midterm penalty epoch = slashing_started_epoch + 2**12 + 1 # account for all remaining epochs remaining_penalty = epoch_penalty[epoch] * 2**12 ``` Expected missed attestation penalty for the current network conditions is ~10^4 Gwei per epoch (~0.0025 ETH daily, or ~0.1 ETH for the whole slashing period for sporadic slashing cases). To calculate balances on-chain these penalties could be neglected as insignificant (they are diluted by the remaining in-protocol holders). ### Overlapped slashings cases To prevent common griefing cases when one validator got slashed each ~2^12 epochs to prevent withdrawals requests finalization, it's decided to socialized penalties only for slashings registered in the day of request or already known but with unresolved midterm penalties. See the diagram [above](#Slashing). ### New slashing events If at least one new CL-wide slashing (i.e., even non-Lido) was registered before the reported epoch, then Oracle is allowed to finalize withdrawal request only using funds on the withdrawal credentials (without additional buffers) during the report. This one addresses the following expectations: - a new client version contains a bug leading to slashings and it is not propagated yet for Lido validators - one of the NOs who also runs validators without Lido but within the same/similar environment got slashed and it is not propagated yet for Lido validators slice CDP ### Timelock There is an artificial timelock to pick up withdrawal requests happened only before some time till reported block N. The purpose is to ensure that all slashings got catched and reported (especially, for attesters) for the finalized epoch by Oracle. Timelock value is decided to be set as 10 epochs (~1 hour). Another consideration to support timelock is having time margin to estimate and decide needed validator exits before the actual Oracle report. The diagram below represents the timelock mechanics (for simplicity: no slashing events). TODO: emphasize meaning of the diagram maybe separate one () ```mermaid gantt title Timelock axisFormat %H:%M dateFormat DD-HH-mm-ss section begin Previous oracle report (epoch0 + X) : milestone, m1, 01-12-00-00,1min section requests allowed to finalize Request 1 : milestone, m2, 01-10-50-00,1min Request 2 : milestone, m3, 01-18-59-22,1min Request 3 : milestone, m4, 02-10-20-00,1min section timelock Previous block N (epoch0): milestone, m5, 01-11-30-00,1min New period start (epoch0 - 10): milestone, m6, 01-10-26-00,1min Allowed period for the finalizable withdrawals requests on expected report : a1, 01-10-26-00,1d New period end (epoch0 + 225 - 10): milestone, m7, 02-10-26-00,1min Expected block N (epoch0 + 225): milestone, m10, 02-11-30-00,1min section requests to carry over for the next period Request 4 : milestone, m8, 02-10-30-00,1min Request 5 : milestone, m9, 02-11-30-00,1min section end Expected oracle report (epoch0 + 225 + Y) : milestone, m4, 02-12-05-00,1min ``` ## Security considerations ### Withdrawal requests limits There are three limits introduced to mitigate possible overspending due to flooding and denial of service in general. ##### 1. Minimum stETH amount to withdraw per single request The `WithdrawalQueue.MIN_STETH_WITHDRAWAL_AMOUNT` value prevents attempts to put tons of dust withdrawal requests since their execution are costly for protocol (in terms of oracles' gas spending). ##### 2. Maximum stETH amount to withdral per single request The `WithdrawalQueue.MAX_STETH_WITHDRAWAL_AMOUNT` value prevents Lido's withdrawal queue clogging to serve only one huge withdrawal (since withdrawal requests are executed in the FIFO order). ##### 3. Validators exiting requests rate limit The `ValidatorsExitBusOracle.getCurrentExitingValidatorsLimit()` is checked to prevent massive spurious validators exiting requests. ##### 4. Withdrawal request fulfillment timelock The `WithdrawalQueue.MIN_FINALIZATION_BLOCKS` prevents slashing events upsides and short-term arbs. ##### 5. Pause/resume withdrawals on-chain The `WithdrawalQueue.pauseWithdrawals` and `WithdrawalQueue.resumeWithdrawals` are intended to temprorary pause withdrawals in case of incidents. Paused state prevents finalizing of the non-finalized yet withdrawals requests. ### Oracle trust assumptions Both of the proposed Oracles are trusted and depends on the committee's threshold quorum value. The 'quorum' word meaning follows [LIP-2](https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-2.md#change-the-meaning-of-quorum). Here is the list of additional sanity checks on top of [LIP-2](https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-2.md#sanity-checks-the-oracles-reports-by-configurable-values): - allowed exited validators relative increase - allowed wc buffered ether (?) - allowed request id to finalize up to - allowed finalized share price TODO: revisit existing sanity checks ### Upgradability The following contracts are upgradable through the usual Lido DAO Aragon voting process: - WithdrawalQueue - Lido - LidoOracle - ValidatorsExitBusOracle Off-chain oracle is upgradable by means of changing the expected version for the upcoming reports stored on-chain within `LidoOracle`, see [`LidoOracle.setReportAlgorithmVersion`](#Function-setReportAlgorithmVersion). ## Backward compatibility The proposed upgrade breaks backward compatibility with the previous protocol state ([Merge-ready version](https://research.lido.fi/t/announcement-merge-ready-protocol-service-pack/2184)) by allowing withdrawals, changing Oracle data scheme, and changing accounting invariants. [Partial withdrawals](https://github.com/ethereum/consensus-specs/pull/2862) will change the previous [unlimited beacon balance increase invariant](https://github.com/lidofinance/lido-dao/blob/master/contracts/0.4.24/Lido.sol#L510) by redirecting extra validators rewards to the withdrwawal credentials address. An ideal upgrade plan should fit to the following requirements - R1: Start collecting and enqueueing withdrawal request in advance (before the Shanghai/Capella hardfork activated) - R2: Allow huge beacon balances drop in once capella enabled due to first ~three-four days of the huge partial withdrawals (validators' balance exceeding `MAX_EFFECTIVE_BALANCE`(=32 ETH) will be withdrawn once Capella activated) - R3: Start fulfilling withdrawal requests once Shanghai/Capella hardfork activated There are three version of the off-chain oracle: - OV0: pre-upgrade merge-ready version - OV0-1: withdrawals-enabled version without withdrawals fulfillment (i.e., reports data according to the proposed scheme, but the last finalized withdrawal request id is always 0) - OV1: withdrawals-enabled version with withdrawals fulfillment An on-chain contracts set has two versions: - C0: pre-upgrade merge-ready version (will report slashing once skimming enabled) - C1: withdrawals-enabled version Rough outline of the possible upgrade plan: 1. Deploy Oracle daemon allowing to conditionally report data (OV0 for C0 and OV0-1 for C1). API version is decided by reading the Lido contract version on-chain via [Lido Repo#getLatest()](https://etherscan.io/address/0xF5Dc67E54FC96F993CD06073f71ca732C1E654B1#readProxyContract#F14). 2. (*implements R1*). Deploy C1 smart contracts (impose API version switch) to start collecting withdrawal requests 3. (*implements R2*). Deploy Oracle daemon allowing to enable/disable withdrawals fulfillment (OV0-1 and OV1, default is OV0-1) 4. (*implements R3*). Run DAO voting to bump the oracle algorithm version with [`LidoOracle.setReportAlgorithmVersion(ov1, t_capella)`](#Function-setReportAlgorithmVersion) (e.g. enable fulfillment with OV1) starting from the provided timestamp `t_capella` (equals to the target Shanghai/Capella fork-choice timestamp). ## Test cases ### Base scenarios - Protocol upgrade contract invariants - Happy path from the forked state - Simple slashing from the forked state - Large withdrawal request - Skipped oracle reports - Large protocol rebase ### Math correctness - Coverage application (don't account as rewards) - Full withdrawal of the whole protocol - Double accounting pre- and post- report - Long-term simulation - Redeemed amounts ### Stressed states - Initial flood of skimmed rewards on Capella activation - Overlapped slashing scenarios - Flood of small withdrawal requests - Large amount of keys to exit (Ethereum queue clogging) ### Gas consumption - Gas usage to emit exiting keys - Gas usage to fill out withdrawal requests - Staker flow overall gas costs ### Failure modes - Majority of validators refuse to exit - Outstanding massive slashing - No oracle reports for a long period - Bank run due to market conditions ## References * [Reference implementation (WIP)](https://github.com/lidofinance/lido-dao/pull/446) * [LIP-2. Oracle contract upgrade v2](https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-2.md) * [LIP-6. In-protocol coverage application mechanism](https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-6.md) * [LIP-7. Composite oracle beacon report receiver](https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-7.md) * [LIP-10. Proxy initializations and LidoOracle upgrade](https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-10.md) * [LIP-12. On-chain part of the rewards distribution after the Merge](https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-12.md) * [LIP-14. Protocol safeguards. Staking rate limiting](https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-14.md) * [Consensus Layer. Withdrawals tracking issue](https://github.com/ethereum/consensus-specs/issues/2758) * [Consensus Layer `get_flag_index_deltas` (assign rewards and penalties)](https://eth2book.info/bellatrix/part3/helper/accessors#def_get_flag_index_deltas) * [Prysm docs. Validator lifecycle](https://docs.prylabs.network/docs/how-prysm-works/validator-lifecycle)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully