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
      • Invitee
    • 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
    • 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 Sharing URL Help
Menu
Options
Versions and GitHub Sync 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
Invitee
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
# Triggerable Withdrawals Technical Specification ## 1. Context The **Triggerable Withdrawals (TW)** mechanism is a necessary extension for the Lido Protocol, enabling the initiation of validator exits without requiring the involvement of a Node Operator. TW is based on [EIP-7002](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7002.md), which addresses a key issue in delegated staking. Previously, stakers had to rely on the goodwill of Node Operators - either to pre-sign an exit message or to agree to process it in the future. This limitation is now removed: any party with access to a validator’s withdrawal credentials can initiate its exit directly via the Execution Layer. The next upgrade for Lido includes the following protocol improvements: - TW support means a substantial reduction in trust assumptions toward Node Operators, which makes it safer to support permissionless stake module as CSM. - A mechanism for **emergency validator exits**, in case of key loss; - **Direct DAO interaction**, enabling the Lido DAO to request validator exits independently of Oracles. - Reporting of **late validators** is now **permissionless**. Previously, this was handled by the Accounting Oracle. ### Goal Implement a Triggerable Withdrawal framework within the Lido protocol to enable permissionless, secure, and verifiable validator exits via the Execution Layer - enhancing the protocol’s fault tolerance, reducing trust assumptions, and paving the way for truly permissionless staking in Lido. ### Related Links - [**EIP-7002: Execution-layer Triggerable Exits**](https://eips.ethereum.org/EIPS/eip-7002) The primary standard introducing a precompile contract for initiating validator exits via the Execution Layer, without requiring the validator's key. - [**EIP-7685: EL Request Interface**](https://eips.ethereum.org/EIPS/eip-7685) A general interface specification for contracts that submit and process requests to the Execution Layer (e.g., exits, consolidations, etc.). - [**CSM V2: EIP-7002 support**](https://hackmd.io/@lido/HJrMPHUt0#EIP-7002-support) Description of TW-related requirements from the Curated Staking Module (CSM), including cases involving lost validator keys or long-term performance failures. - [**Oracles technical details**](https://docs.lido.fi/guides/oracle-operator-manual/#oracle-phases) Description of how on-chain and off-chain oracles work. ## 2. Definitions and Terminology ### **Validators Exit Bus (VEB)** An on-chain contract that serves as the central infrastructure for managing validator exit requests. It stores report hashes, emits exit events, and maintains data and tools that enables anyone to prove a validator was requested to exit. Unlike VEBO, it supports exit reports from a wide range of entities. ### **Validators Exit Bus Oracle (VEBO)** A component of the oracle system responsible for delivering exit request reports to the VEB on behalf of oracles. Its primary role is to initiate the exit of enough validators to efficiently fulfill pending withdrawal requests. It is a part of the VEB contract. ### Validator Exit Request Events generated within the VEB that signal to Node Operators the need to initiate exits for the validators specified in the corresponding event. ### **Triggerable Withdrawal** A validator exit process initiated via the **EL** without requiring a signature from the validator key, using withdrawal credentials and the precompile contract described in [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002). ### Triggerable Withdrawal Request (TWR) A request to initiate a validator exit through the EL. ### Triggerable Withdrawals Gateway (TWG) A new smart contract that serves as the single entry point for all TWRs in the protocol. Responsible for enforcing rate limits, verifying roles and permissions before forwarding EL requests to the Withdrawal Vault. ### Late **Validator** A validator that has not begun the exit process within the predefined exit time window following the emission of an exit request event in VEB. Such validators can be reported permissionlessly and forwarded to the staking module for potential penalties. ### Validator Exit Delay Verifier A smart contract that exposes a public, permissionless interface for reporting late validators and forwarding them to Staking Modules, which may apply penalties based on module-specific rules. ## 3. Requirements and Scope ### Functional Requirements - **Authorized entities** (e.g., governance) must be able to request specific validators to exit via the VEB; - **Authorized entities** (e.g., CSM) must be able to create TWRs; - **Anyone** must be able to create a TWR for a validator that was requiested to exit via the VEB; - **Anyone** must be able to submit proof that a validator is late in exiting, allowing the Staking Module to take further action; ### Non-functional requirements - Define clear requirements for trusted entities integrating with the Triggerable Withdrawals framework. - Minimize trust assumptions on privileged actors such as Node Operators and Oracles; - Mitigate potential attack vectors arising from manipulation or spam using new permissionless methods; ### Scope The Triggerable Withdrawals framework introduces a set of changes across both on-chain and off-chain components: On-chain Components: - [Lido Locator](https://github.com/lidofinance/core/blob/feat/triggerable-exits/contracts/0.8.9/LidoLocator.sol) - [Validators Exit Bus](https://github.com/lidofinance/core/blob/feat/triggerable-exits/contracts/0.8.9/oracle/ValidatorsExitBus.sol) - [Validators Exit Bus Oracle](https://github.com/lidofinance/core/blob/feat/triggerable-exits/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol) - [Accounting Oracle](https://github.com/lidofinance/core/blob/feat/triggerable-exits/contracts/0.8.9/oracle/AccountingOracle.sol) - [Withdrawal Vault](https://github.com/lidofinance/core/blob/feat/triggerable-exits/contracts/0.8.9/WithdrawalVault.sol) - [WithdrawalVaultEIP7002](https://github.com/lidofinance/core/blob/feat/triggerable-exits/contracts/0.8.9/WithdrawalVaultEIP7002.sol) - [Staking Router](https://github.com/lidofinance/core/blob/feat/triggerable-exits/contracts/0.8.9/StakingRouter.sol) - [Node Operator Registry](https://github.com/lidofinance/core/blob/feat/triggerable-exits/contracts/0.4.24/nos/NodeOperatorsRegistry.sol) - [New] [Validator Exit Delay Verifier](https://github.com/lidofinance/core/blob/feat/triggerable-exits/contracts/0.8.25/ValidatorExitDelayVerifier.sol) - [New] [Triggerable Withdrawals Gateway](https://github.com/lidofinance/core/blob/feat/triggerable-exits/contracts/0.8.9/TriggerableWithdrawalsGateway.sol) Off-chain Components: - [Validator Exit Bus Oracle](https://github.com/lidofinance/lido-oracle/pull/685) - [Accounting Oracle](https://github.com/lidofinance/lido-oracle/pull/685) - [Validator Ejector](https://github.com/lidofinance/validator-ejector) - Monitoring - [New] Trigger Exits Bot - [New] Validator Late Prover Bot ## 4. Architecture and Scenarios At a high level, the architecture is centered around the **VEB**, which serves as the main coordination point for validator exit requests. Exit reports can be submitted to the VEB by trusted entities such as oracles or governance, which then emit validator exit events. Once exit request events are emitted, Node Operators are expected to voluntarily initiate the validator exit. The **VEBO** is an extension of the **VEB**, designed to support oracle-driven reporting. While VEB serves as the central hub for coordinating exit events, VEBO adds an oracle-specific layer that enables oracles to submit and confirm exit reports. To manage all **TWRs** submitted by authorized entities, a dedicated smart contract called the **Triggerable Withdrawals Gateway (TWG)** is introduced. This contract acts as the single entry point for TWRs from trusted sources and forwards valid requests to the **Withdrawal Vault**. It is responsible for: - Validating the roles and permissions of the caller; - Enforcing rate limits for safety and protocol stability; - Coordinating with the `Withdrawal Vault` to trigger validator exits via the Execution Layer (EL); - Refunds any extra value provided to cover fees from the 7002 contract. Once an exit event has been emitted via the VEB, **any participant** can submit a TWR through a permissionless interface provided by the VEB contract. VEB ensures that the validator was indeed requested to exit before passing the request to the TWG for execution. Additionally, the architecture includes a **Validator Exit Delay Verifier**, a contract that allows anyone to report late validators—those that failed to begin exiting within the expected time window after an exit event was emitted. These reports are submitted on-chain and forwarded to the appropriate staking modules, which may apply penalties based on module-specific logic. ### 4.1 Report Validators to Exit This flow describes the **two-phase delivery model** for reporting validators to exit. In the first phase, Oracles - via consensus on a smart contract, [Easy Track Factories](https://docs.lido.fi/guides/easy-track-guide/) or governance submit a hash of the exit requests data to the **VEB**. The report data includes a list of validators to be exited. The second phase involves revealing the actual report data and emitting exit events for the listed validators. When the data is revealed, the contract saves a timestamp linked to the data hash. This later allows any actor to prove that a specific validator was requested to exit, and when the corresponding event was emitted. Because report hashes can be submitted by more than just oracles, this system increases protocol resilience and moves Lido toward a more decentralized architecture. ![image](https://hackmd.io/_uploads/ryswhbsGgg.png) ### 4.2 Triggerable Full Withdrawal Requests submittion This scenario describes the creation of **Triggerable Withdrawals** flows through the **TW Gateway (TWG)**. There are two separate flows for triggering withdrawals, depending on the actor: - Creating a TWR through the **VEB**; - Creating a TWR through the **CSM**. Both flows go through the **TWG**, which checks the caller contract roles, enforces exit limits, refunds any extra value provided, and forwards the request to the Withdrawal Vault. After that, it notifies the Staking Router with information about which validator was requested to exit and the associated fee. [Source code](https://github.com/lidofinance/core/blob/develop/contracts/0.8.9/oracle/ValidatorsExitBus.sol#L310) ![image](https://hackmd.io/_uploads/BkY_nZiMxl.png) --- **Submitting TWR** through the **VEB** Any actor can trigger a validator exit that was previously requested through the **VEB**, as soon as the report has been revealed. The participant uses the previously submitted report data and specifies the validator indices (in an array) to be exited. The **VEB** then performs the following checks: - The provided exit data hash exists and matches the hash stored in the VEB. - The corresponding data has already been revealed. Once validated, the request is forwarded to the **TWG** — the central contract handling all triggerable exit requests. The **TWG**: - Enforces frame-level rate limits. - Forwards the request to the **Withdrawal Vault**, which interacts with the **EIP-7002 precompile** to trigger the actual exit. - Notifies the associated staking module. - Refunds any extra value provided. --- **Submitting TWR through CSM Ejector** This flow mirrors the previous one, but instead of using validators that were requested to exit, **trusted entities** can directly initiate an exit **without any prior exit request event in the VEB**. This direct exit flow is available to specific entities such as **CSM**, but due to its powerful nature, modules must adhere to strict internal checks before using it. These include: - Verifying that the validator key belongs to the module. - Ensuring the key was deposited through a **DSM**. - Ensuring there are no key duplicates in one transaction to avoid dry out TWG limits. - Implementing additional logic to confirm that the entity calling the **TW** is authorized (e.g., owns the key or is creating a TWR for a validator that is permitted to exit). With proper validation and verifiable on-chain code, this mechanism allows modules to trigger validator exits **without increasing trust assumptions** toward Node Operators or third parties. Details on the checks performed by CSM can be found [here](https://hackmd.io/c37ab5evRLSwXb_WG9QKMg). ### 4.3 Report Late Validators A **Late Validator** is a validator that was requested to exit via the VEB, but failed to begin exiting on the CL within a defined window (e.g., N slots or epochs). To allow staking modules to penalize such behavior, late validators must be explicitly reported on-chain. Previously, this was handled via the **AO** by reporting the count of stuck validators. However, this approach was inflexible, fully off-chain dependent, and prone to race conditions — a validator might have exited just before the AO reported it as stuck. With the new flow, **any actor** can report a validator as "late" as soon as it misses its expected exit window — even by a single slot. This makes the penalty mechanism more precise and unavoidable. To do this, a participant must: - Submit a **proof of the validator’s current state** on the CL; - Reference the VEB report in which the validator was requested to exit. The system calculates the difference between: - The timestamp of the exit request emission (from VEB) or validator’s `activation epoch + COMMITTEE_PERIOD`. [Details](https://www.notion.so/Triggerable-Withdrawals-Technical-Specification-1dcbf633d0c98019806ed1969ef6cd3d?pvs=21); - The timestamp of the CL state showing the validator is still active. This time delta is passed to the **staking module**, which uses its internal logic to decide whether a penalty should be applied to the Node Operator and what kind of action is warranted. ![image](https://hackmd.io/_uploads/S1wa2bjzel.png) ## 5. Onchain Components This section provides a detailed breakdown of each smart contract involved in the Triggerable Withdrawals framework, including their purpose, responsibilities, functions, and external interfaces. ### 5.1 Validator Exit Bus The Validator Exit Bus is the core contract for coordinating validator exit requests within the Lido protocol. **Responsibilities** - Emitting `ValidatorExitRequest` events based on unpacked exit requests. - Enforcing rate limits on the number of exits per block. - Tracking exit report history and providing tools to prove that a validator was requested to exit. --- **Removal of Last Requested Validator Indexes from On-Chain Storage** Since reports will be delivered not only by Oracles, the contract can no longer guarantee that all validators under a single Node Operator will be requested to exit in order based on their validator indexes on the CL. As a result, functionality relying on this assumption becomes obsolete. With the introduction of TW, it is expected that once validators are marked as delayed, they will be exited through the TW framework. Therefore, all off-chain applications that previously relied on ordered exits can simply monitor the validator’s state on the CL and read the latest events to determine which validators have been requested to exit or have already exited. ```solidity /// @dev Storage slot: mapping(uint256 => RequestedValidator) lastRequestedValidatorIndices /// A mapping from the (moduleId, nodeOpId) packed key to the last requested validator index. bytes32 internal constant LAST_REQUESTED_VALIDATOR_INDICES_POSITION = keccak256("lido.ValidatorsExitBusOracle.lastRequestedValidatorIndices"); function getLastRequestedValidatorIndices(uint256 moduleId, uint256[] calldata nodeOpIds) view; ``` --- **Pausable Contract** The **Validator Exit Bus** contract is pausable using the [GateSeal](https://github.com/lidofinance/gate-seals?tab=readme-ov-file#what-is-a-gateseal) contract to prevent unexpected behavior that could harm the protocol. When the contract is paused, it prevents report submissions, report unpacking, and triggering of exits through the VEB. --- #### **5.1.1 submitExitRequestsHash** Saves a new hash of exit requests data submitted by a trusted entity. **Functionality:** - Stores the submitted hash along with the current contract version. - Emits a `RequestsHashSubmitted` event. - Saves actual contract version. - Interface ```solidity /** * @notice Submit a hash of the exit requests data. * * @dev Reverts if: * - The contract is paused. * - The caller does not have the `SUBMIT_REPORT_HASH_ROLE`. * - The hash has already been submitted. * * Emits `RequestsHashSubmitted` event; * * @param exitRequestsHash - keccak256 hash of the encoded validators list */ function submitExitRequestsHash(bytes32 exitRequestsHash) external; ``` --- #### **Easy Track Factories for Submitting Report Hashes to VEB** In addition to regular governance (GOV), Easy Track Factories will be implemented for the following actors: - **SDVTMC** – Can manage exits for any validator exits in the **sDVT Staking Module**. - **NO (Node Operator)** – Can manage any exits for validators they own in the **Curated Staking Module**. **Technical details:** [Appendix B - Easy Track Factories for VEB](#Appendix-B---Easy-Track-Factories-for-VEB) --- ##### **5.1.2 submitExitRequestsData** A permissionless function used to unpack data previously submitted via hash. **Functionality:** - Unpacks validators to exit requests and emits `ValidatorExitRequest` events. - Records the last processed index and timestamp. - Interface ```solidity struct ExitRequestsData { // Lists of validators encoded /// MSB <------------------------------------------------------- LSB /// | 3 bytes | 5 bytes | 8 bytes | 48 bytes | /// | moduleId | nodeOpId | validatorIndex | validatorPubkey | bytes data; // Data format uint256 dataFormat; } /** * @notice Method for submitting exit requests data * * @dev Reverts if: * - The contract is paused. * - The keccak256 hash of `requestsData` does not exist in storage (i.e., was not submitted). * - The provided Exit Requests Data has already been submitted. * - The contract version does not match the version at the time of hash was submitted. * - The data format is not supported. * - The data length is less than or equal to the maximum number of requests allowed per payload. * - The data length is less than or equal to the currently available limit quota. * * Emits `ValidatorExitRequest` events; * * @param ExitRequestsData - The exit requests structure. */ function submitExitRequestsData(ExitRequestsData calldata request) external; ``` --- **Limits enforcement** This contract uses the **Limits Library** to enforce rate limiting. Each **Validator Exit Request** consumes one unit of quota. All Validator Exit Requests, except those from Oracle reports, are subject to a single global limit. Oracle reports are limmited with Oracle Sanity Checker contract. Analitics Limit Calculations: https://hackmd.io/5wN10bGaSbyPwpzcVkdVVw?both Technical implmenetation: [**Appendix A – Limit Implementation**](https://www.notion.so/Appendix-A-Limit-Implementation-1f4bf633d0c980069054f720630ca286?pvs=21) Methods to set and read limits - Interfaces ```solidity /** * @notice Returns information about current limits data * @return maxExitRequestsLimit Maximum exit requests limit * @return exitsPerFrame The number of exits that can be restored per frame. * @return frameDuration The duration of each frame, in seconds, after which `exitsPerFrame` exits can be restored. * @return prevExitRequestsLimit Limit left after previous requests * @return currentExitRequestsLimit Current exit requests limit */ function getExitRequestLimitFullInfo() external view returns ( uint256 maxExitRequestsLimit, uint256 exitsPerFrame, uint256 frameDuration, uint256 prevExitRequestsLimit, uint256 currentExitRequestsLimit ) /** * @notice Sets the maximum exit request limit and the frame during which a portion of the limit can be restored. * @param maxExitRequestsLimit The maximum number of exit requests. * @param exitsPerFrame The number of exits that can be restored per frame. * @param frameDuration The duration of each frame, in seconds, after which `exitsPerFrame` exits can be restored. */ function setExitRequestLimit( uint256 maxExitRequestsLimit, uint256 exitsPerFrame, uint256 frameDuration ) external onlyRole(EXIT_REPORT_LIMIT_ROLE) ``` --- **Max validators per report** In addition to limits, a restriction on the number of processed validators per transaction will be applied. This ensures that the report cannot consume excessive gas during processing. - Interface ```solidity /** * @notice Sets the maximum allowed number of validator exit requests to process in a single report. * @param maxRequests The new maximum number of exit requests allowed per report. */ function setMaxValidatorsPerReport(uint256 value) external onlyRole(EXIT_REPORT_LIMIT_ROLE); /** * @notice Returns information about allowed number of validator exit requests to process in a single report. * @return The new maximum number of exit requests allowed per report */ function getMaxValidatorsPerReport() external view returns (uint256); ``` --- **Hash Invalidation** Reports are invalidated if the VEB contract version changes, preventing outdated data from being unpacked. --- #### **5.1.3 Trigger Full Exits** A **permissionless function** used to trigger exits for validators that have already been requested for exit. **Functionality:** - Verifies that `requestsData` was previously delivered and submitted to the VEB. - Forwards all validated data to the **TWG.** - Interface ```solidity /** * @notice Submits Triggerable Withdrawal Requests to the Triggerable Withdrawals Gateway. * * @param exitsData The exit request data previously submitted by the VEB. * @param exitDataIndexes Array of sorted indexes pointing to validators in `exitsData.data` * to be exited via TWR. * @param refundRecipient Address to return extra fee on TW (eip-7002) exit * * @dev Reverts if: * - The hash of `exitsData` was not previously submitted in the VEB. * - The data was not revealed in the second phase. * - Any of the provided `exitDataIndexes` refers to a validator that was not yet unpacked (i.e., exit requests not emitted). * - `exitDataIndexes` is not sorted in ascending order. * - `exitDataIndexes` contains duplicate entries. */ function triggerExits( ExitRequestsData calldata exitsData, uint256[] calldata exitDataIndexes, address refundRecipient ) payable external; ``` --- #### **5.1.4 Public methods** Provide insight into report unpacking history: - Interfaces ```solidity /** * @notice Returns the timestamp when the exit request was delivered. * * @param exitRequestsHash - The exit requests hash. * * @dev Reverts if: * - exitRequestsHash was not submitted * - Request data was not submitted */ function getDeliveryTimestamp(bytes32 exitRequestsHash) external view returns (uint256 deliveryDateTimestamp); ``` Helper method that parses the `exitRequests` structure for supported data formats. - Interface ```solidity /** * @notice Returns validator exit request data by index. * @param exitRequests Encoded list of validator exit requests. * @param dataFormat Format of the encoded exit request data. Currently, only DATA_FORMAT_LIST = 1 is supported. * @param index Index of the exit request within the `exitRequests` list. * @return pubkey Public key of the validator. * @return nodeOpId ID of the node operator. * @return moduleId ID of the staking module. * @return valIndex Index of the validator. */ function unpackExitRequest( bytes calldata exitRequests, uint256 dataFormat, uint256 index ) external pure returns (bytes memory pubkey, uint256 nodeOpId, uint256 moduleId, uint256 valIndex); ``` ### 5.2 **Validator Exit Bus Oracle (VEBO)** VEBO is an extension of VEB designed specifically to support oracle-based report submission. **Differences from VEB:** - Report hash is saved into VEB during data submission (on second phase of VEBO report). - Only Oracle Members can submit report (or someone with `SUBMIT_DATA_ROLE`). **5.2.1 submitReportData** Submits Oracle report data for processing. ```solidity /// @notice Submits report data for processing. /// /// @param data The data. See the `ReportData` structure's docs for details. /// @param contractVersion Expected version of the oracle contract. /// /// Reverts if: /// - The caller is not a member of the oracle committee and doesn't possess the /// SUBMIT_DATA_ROLE. /// - The provided contract version is different from the current one. /// - The provided consensus version is different from the expected one. /// - The provided reference slot differs from the current consensus frame's one. /// - The processing deadline for the current consensus frame is missed. /// - The keccak256 hash of the ABI-encoded data is different from the last hash /// provided by the hash consensus contract. /// - The provided data doesn't meet safety checks. function submitReportData(ReportData calldata data, uint256 contractVersion) external; ``` ### 5.3 Triggerable Withdrawals Gateway The **Triggerable Withdrawals Gateway** is a core contract responsible for receiving and processing all **Triggerable Withdrawal Requests (TWRs)**. It enforces rate limits on TWR creation, calculates the required fee per request, issues refund (if any) to the designated recipient, and forwards validated requests to the **Withdrawal Vault**. After processing, it notifies the **Staking Router** about the request for further action. - Interfaces ```solidity /** * @dev Submits Triggerable Withdrawal Requests to the Withdrawal Vault as full withdrawal requests * for the specified validator public keys. * * @param triggerableExitsData An array of `ValidatorData` structs, each representing a validator * for which a withdrawal request will be submitted. Each entry includes: * - `stakingModuleId`: ID of the staking module. * - `nodeOperatorId`: ID of the node operator. * - `pubkey`: Validator public key, 48 bytes length. * @param refundRecipient The address that will receive any excess ETH sent for fees. * @param exitType A parameter indicating the type of exit, passed to the Staking Module. * * Emits `TriggerableExitRequest` event for each validator in list. * * @notice Reverts if: * - The caller does not have the `ADD_FULL_WITHDRAWAL_REQUEST_ROLE` * - The total fee value sent is insufficient to cover all provided TW requests. * - There is not enough limit quota left in the current frame to process all requests. */ function triggerFullWithdrawals( ValidatorData[] calldata triggerableExitsData, address refundRecipient, uint8 exitType ) external payable onlyRole(ADD_FULL_WITHDRAWAL_REQUEST_ROLE); ``` **Limits enforcement** This contract uses the **Limits Library** to enforce rate limiting. Each **TWR** consumes **one unit of quota**. All TWRs, regardless of their source, are subject to a **single global limit**, ensuring that the total number of processed requests remains within the protocol-defined capacity. Analytics numbers: https://hackmd.io/5wN10bGaSbyPwpzcVkdVVw?both Technical implementation: [**Appendix A – Limit Implementation**](https://www.notion.so/Appendix-A-Limit-Implementation-1f4bf633d0c980069054f720630ca286?pvs=21) ### 5.4 Withdrawal Vault The **Withdrawal Vault** is a core contract capable of performing **Execution Layer (EL) requests**. We implement support for Triggerable Withdrawals: - [EIP-7002: Execution Layer Triggerable Withdrawals](https://eips.ethereum.org/EIPS/eip-7002) **(Triggerable Withdrawal Requests)** - Interfaces ```solidity /** * @dev Retrieves the current EIP-7002 withdrawal fee. * @return The minimum fee required per withdrawal request. */ function getWithdrawalRequestFee() external view returns (uint256) { return _getRequestFee(WITHDRAWAL_REQUEST); } /** * @dev Submits EIP-7002 full or partial withdrawal requests for the specified public keys. * Each full withdrawal request instructs a validator to fully withdraw its stake and exit its duties as a validator. * Each partial withdrawal request instructs a validator to withdraw a specified amount of ETH. * * @param pubkeys A tightly packed array of 48-byte public keys corresponding to validators requesting partial withdrawals. * | ----- public key (48 bytes) ----- || ----- public key (48 bytes) ----- | ... * * @param amounts An array of 8-byte unsigned integers representing the amounts to be withdrawn for each corresponding public key. * For full withdrawal requests, the amount should be set to 0. * For partial withdrawal requests, the amount should be greater than 0. * * @notice Reverts if: * - The contract is paused * - The caller does not have the `ADD_WITHDRAWAL_REQUEST_ROLE`. * - The provided public key array is empty. * - The provided public key array malformed. * - The provided public key and amount arrays are not of equal length. * - The provided total fee value is invalid. */ function addWithdrawalRequests( bytes calldata pubkeys, uint64[] calldata amounts ) external payable; ``` ### 5.5 Validator **Exit Delay** Verifier The **Validator Exit Delay Verifier** is a contract responsible for detecting **late validators**—validators that were requested to exit via the VEB but have **not yet initiated exit on the CL**. It calculates the time during which Node Operators have failed to exit their validators and provides this information to **Staking Modules** via the **Staking Router**. This is done by submitting **proofs** that: - The validator **has no exit epoch set** on CL at a known slot; - The validator **was requested to exit** via a previously submitted VEB report. - Interfaces ```solidity struct ProvableBeaconBlockHeader { BeaconBlockHeader header; // Header of the block which root is known at 'rootsTimestamp'. uint64 rootsTimestamp; // Timestamp passed to EIP-4788 block roots contract to retrieve the known block root. } struct ExitRequestsData { bytes data; uint256 dataFormat; } struct ValidatorWitness { // The index of an exit request in the VEBO exit requests data uint32 exitRequestIndex; // -------------------- Validator details ------------------- bytes32 withdrawalCredentials; uint64 effectiveBalance; bool slashed; uint64 activationEligibilityEpoch; uint64 activationEpoch; uint64 withdrawableEpoch; // ------------------------ Proof --------------------------- bytes32[] validatorProof; } /** * @notice Verifies that the provided validators were not requested to exit on the CL after a VEB exit request. * Reports exit delays to the Staking Router. * @dev Ensures that `exitEpoch` is equal to `FAR_FUTURE_EPOCH` at the given beacon block. * @param beaconBlock The block header and EIP-4788 timestamp to prove the block root is known. * @param validatorWitnesses Array of validator proofs to confirm they are not yet exited. * @param exitRequests The concatenated VEBO exit requests, each 64 bytes in length. */ function verifyValidatorExitDelay( ProvableBeaconBlockHeader calldata beaconBlock, ValidatorWitness[] calldata validatorWitnesses, ExitRequestsData calldata exitRequests ) external; /** * @notice Verifies that the provided validators were not requested to exit on the CL after a VEB exit request. * Reports exit delays to the Staking Router. * @dev Ensures that `exitEpoch` is equal to `FAR_FUTURE_EPOCH` at the given beacon block. * @dev Verifies historical blocks (via historical_summaries). * @dev The oldBlock.header must have slot >= FIRST_SUPPORTED_SLOT. * @param beaconBlock The block header and EIP-4788 timestamp to prove the block root is known. * @param oldBlock Historical block header witness data and its proof. * @param validatorWitnesses Array of validator proofs to confirm they are not yet exited in oldBlock.header. * @param exitRequests The concatenated VEBO exit requests, each 64 bytes in length. */ function verifyHistoricalValidatorExitDelay( ProvableBeaconBlockHeader calldata beaconBlock, HistoricalHeaderWitness calldata oldBlock, ValidatorWitness[] calldata validatorWitnesses, ExitRequestData calldata exitRequests ) external; ``` --- **Calculating `secondsSinceEligibleExitRequest` Sent to the Staking Module** It is important to note that `secondsSinceEligibleExitRequest` is **not always equal** to the raw difference between the timestamp when the validator was requested to exit and the timestamp when the validator is still observed as active. In edge cases, a Node Operator **may not be able to initiate an exit immediately**—for example, if the validator was only recently activated. Therefore, this value represents the time **between the moment the Node Operator was first eligible to initiate the exit** (i.e., after activation and exit eligibility conditions were met) **and the moment the validator is still observed as active**. ```solidity // The earliest a validator can voluntarily exit is after the Shard Committee Period // subsequent to its activation epoch. uint256 earliestPossibleVoluntaryExitTimestamp = GENESIS_TIME + (activationEpoch * SLOTS_PER_EPOCH * SECONDS_PER_SLOT) + SHARD_COMMITTEE_PERIOD_IN_SECONDS; // The actual eligible timestamp is the max between the exit request submission time // and the earliest possible voluntary exit time. uint256 eligibleExitRequestTimestamp = reportDeliveredTimestamp > earliestPossibleVoluntaryExitTimestamp ? deliveredTimestamp : earliestPossibleVoluntaryExitTimestamp; secondsSinceEligibleExitRequest **=** referenceSlotTimestamp - eligibleExitRequestTimestamp; ``` ### 5.6 Staking Router The main update introduces support in the **Staking Router** for a new interface that staking modules must implement. This interface includes two new methods: - One for reporting **late validators**; - One for reporting all **triggerable withdrawal requests** that have been executed for validators managed by the module. Additionally, all legacy logic related to **stuck keys** is being deprecated. - Interfaces ```solidity /// @notice Handles tracking and penalization logic for a validator that remains active beyond its eligible exit window. /// @dev This function is called to report the current exit-related status of a validator belonging to a specific node operator. /// It accepts a validator's public key, associated with the duration (in seconds) it was eligible to exit but has not exited. /// This data could be used to trigger penalties for the node operator if the validator has been non-exiting for too long. /// @param _stakingModuleId The ID of the staking module. /// @param _nodeOperatorId The ID of the node operator whose validator status is being delivered. /// @param _proofSlotTimestamp The timestamp (slot time) when the validator was last known to be in an active ongoing state. /// @param _publicKey The public key of the validator being reported. /// @param _eligibleToExitInSec The duration (in seconds) indicating how long the validator has been eligible to exit but has not exited. function reportValidatorExitDelay( uint256 _stakingModuleId, uint256 _nodeOperatorId, uint256 _proofSlotTimestamp, bytes calldata _publicKey, uint256 _eligibleToExitInSec ) external; /// @notice Handles the triggerable exit event for a validator belonging to a specific node operator. /// @dev This function is called when a validator is exited using the triggerable exit request on the Execution Layer (EL). /// @param _stakingModuleId The ID of the staking module. /// @param _nodeOperatorId The ID of the node operator. /// @param _publicKey The public key of the validator being reported. /// @param _withdrawalRequestPaidFee Fee amount paid to send a withdrawal request on the Execution Layer (EL). /// @param _exitType The type of exit being performed. /// This parameter may be interpreted differently across various staking modules, depending on their specific implementation. function onValidatorExitTriggered( uint256 _stakingModuleId, uint256 _nodeOperatorId, bytes calldata _publicKey, uint256 _withdrawalRequestPaidFee, uint256 _exitType ) external; ``` ### 5.7 IStakingModule More details about new interface could be found here: [[TW] ADR: IStakingModule](https://www.notion.so/TW-ADR-IStakingModule-1d6bf633d0c9802fbb0ddb8128f0fc8a?pvs=21) . - Interfaces ```solidity /// @notice Handles tracking and penalization logic for a validator that remains active beyond its eligible exit window. /// @dev This function is called by the StakingRouter to report the current exit-related status of a validator /// belonging to a specific node operator. It accepts a validator's public key, associated /// with the duration (in seconds) it was eligible to exit but has not exited. /// This data could be used to trigger penalties for the node operator if the validator has exceeded the allowed exit window. /// @dev Reverts if the function call would have no effect on the node operator (i.e., no new penalization will be applied). /// /// @param _nodeOperatorId The ID of the node operator whose validator's status is being delivered. /// @param _proofSlotTimestamp The timestamp (slot time) when the validator was last known to be in an active ongoing state. /// @param _publicKey The public key of the validator being reported. /// @param _eligibleToExitInSec The duration (in seconds) indicating how long the validator has been eligible to exit but has not exited. function reportValidatorExitDelay( uint256 _nodeOperatorId, uint256 _proofSlotTimestamp, bytes calldata _publicKey, uint256 _eligibleToExitInSec ) external; /// @notice Determines whether a validator's exit status should be updated and will have an effect on the Node Operator. /// @param _nodeOperatorId The ID of the node operator. /// @param _proofSlotTimestamp The timestamp (slot time) when the validator was last known to be in an active ongoing state. /// @param _publicKey The public key of the validator. /// @param _eligibleToExitInSec The number of seconds the validator was eligible to exit but did not. /// @return bool Returns true if the contract should receive the updated status of the validator. function isValidatorExitDelayPenaltyApplicable( uint256 _nodeOperatorId, uint256 _proofSlotTimestamp, bytes calldata _publicKey, uint256 _eligibleToExitInSec ) external view returns (bool); /// @notice Handles the triggerable exit event for a validator belonging to a specific node operator. /// @dev This function is called by the StakingRouter when a validator is exited using the triggerable /// exit request on the Execution Layer (EL). /// @param _nodeOperatorId The ID of the node operator. /// @param _publicKey The public key of the validator being reported. /// @param _withdrawalRequestPaidFee Fee amount paid to send a withdrawal request on the Execution Layer (EL). /// @param _exitType The type of exit being performed. /// This parameter may be interpreted differently across various staking modules, depending on their specific implementation. function onValidatorExitTriggered( uint256 _nodeOperatorId, bytes calldata _publicKey, uint256 _withdrawalRequestPaidFee, uint256 _exitType ) external; /// @notice Returns the number of seconds after which a validator is considered late. /// @return uint256 The exit deadline threshold in seconds for all node operators. function exitDeadlineThreshold(uint256 _nodeOperatorId) public view returns (uint256); ``` ### 5.8 Node Operator Registry and sDVT In the **Node Operators Registry**, all logic related to **stuck keys** and corresponding penalties will be removed. This update applies to both the **Curated Module** and the **sDVT Module**. When a late validator is reported, the module will not take any direct action other than **emitting a late event**. The same applies when a validator is exited through a triggerable withdrawal — the event will be emitted, but no penalty logic will be executed. ### **Node Operator Registry Changes:** - Implement the `IStakingModule` interface. - Deprecate all logic related to the old stuck keys reporting and penalization mechanisms. - Interface ```solidity /// @notice Sets the validator exit deadline threshold and the reporting window for late exits. /// @dev Updates the cutoff timestamp before which validators are protected from penalization. /// Prevents penalizing validators whose exit eligibility began before the new policy took effect. /// @param _threshold The number of seconds a validator has to exit after becoming eligible. /// @param _reportingWindow The additional number of seconds during which a late exit can still be reported. function setExitDeadlineThreshold(uint256 _threshold, uint256 _reportingWindow) external; /// @notice Returns whether the provided validator has already been reported as late. /// @return bool True if the Node Operator has already been penalized for the validator’s delayed exit. function isValidatorPenalized(bytes calldata _publicKey) external view returns (bool); /// @notice Returns the cutoff timestamp before which validators cannot be penalized for delayed exits. /// @return uint256 The cutoff timestamp used when evaluating late exits. function exitPenaltyCutoffTimestamp() external view returns (uint256); ``` **5.8.1 setExitDeadlineThreshold** The `setExitDeadlineThreshold` function also sets the `nonPenalizableBefore` timestamp to: `current_timestamp - current_threshold - reporting_window`, where `reporting_window` defines the period during which the **Late Validator Bot** can report late exits. All validators that were requested to exit before this timestamp **cannot be reported** after the exit deadline is changed. This ensures that previously exited or outdated validators are not penalized under newly introduced policies (e.g., if the threshold was reduced). There is an edge case where the bot might report a recently requested validator as late **before the governance vote to reduce the exit period is enacted**. Although the validator was originally allowed more time to exit, it would still be marked as late. Such cases will be handled operationally by **announcing the policy change in advance before the governance vote is enacted**. This way, despite the on-chain parameters not yet being updated, Node Operators will already be expected to follow the new policy approved by the DAO through a Snapshot vote. ### 5.9 Lido Locator The following new contract addresses are planned to be added to the **Lido Locator**: - `TriggerableWithdrawalsGateway` - `ValidatorExitDelayVerifier` - ABI ```json [ {"inputs":[ {"components":[ {"internalType":"address","name":"triggerableWithdrawalsGateway","type":"address"}, {"internalType":"address","name":"validatorExitDelayVerifier","type":"address"}, ... ] } } ] ``` ### 5.10 Accounting Oracle The Accounting Oracle is no longer responsible for delivering stuck keys to the staking modules. Therefore, the data format related to stuck keys has been deprecated. ```solidity= function _processExtraDataItems(bytes calldata data, ExtraDataIterState memory iter) internal { // ... if (itemType == EXTRA_DATA_TYPE_STUCK_VALIDATORS) { revert DeprecatedExtraDataType(index, itemType); } // ... } ``` ## 6. **Appendix** ### **Appendix A – Limit Implementation** To protect the Lido protocol from excessive validator exits and prevent abuse, a **rate-limiting mechanism** is introduced for VEB and TWG. This mechanism enforces a dynamic quota system that gradually grows over time and is consumed as exits are triggered. **Limit Mechanics** The system is governed by three core parameters: 1. `maxExitRequestsLimit` The maximum capacity of the quota. The available quota cannot exceed this value. With time, the quota is gradually replenished — up to this maximum. 2. `exitsPerFrame` Defines how much new quota becomes available with each new frame. This enables gradual and predictable replenishment of the quota over time. 3. `frameDuration` The duration of each frame, in seconds, after which `exitsPerFrame` exits can be restored. **Track Remaining Quota** The system keeps `prevExitRequestsLimit` — the remaining quota after the last usage. When a new request arrives, the system checks how much time has passed since `prevTimestamp`. It calculates how many full `frameDuration` intervals (frames) have elapsed. For each full frame passed, `exitsPerFrame` units are restored. The updated quota is calculated as:`restoredQuota = prevExitRequestsLimit + framesPassed * exitsPerFrame`, but it is capped at `maxExitRequestsLimit`. The request amount is subtracted from the restored quota. This new value becomes the `prevExitRequestsLimit` for future calculations. The `prevTimestamp` is advanced by `framesPassed * frameDuration`, anchoring the system for the next round of quota restoration. - Interface ```solidity library ExitLimitUtilsStorage { function getStorageExitRequestLimit(bytes32 _position) internal view returns (ExitRequestLimitData memory data); function setStorageExitRequestLimit(bytes32 _position, ExitRequestLimitData memory _data) internal; } library ExitLimitUtils { function calculateCurrentExitLimit( ExitRequestLimitData memory _data, uint256 timestamp ) internal pure returns (uint256 currentLimit); function updatePrevExitLimit( ExitRequestLimitData memory _data, uint256 newExitRequestLimit, uint256 timestamp ) internal pure returns (ExitRequestLimitData memory); function setExitLimits( ExitRequestLimitData memory _data, uint256 maxExitRequestsLimit, uint256 exitsPerFrame, uint256 frameDuration, uint256 timestamp ) internal pure returns (ExitRequestLimitData memory); function isExitLimitSet(ExitRequestLimitData memory _data) internal pure returns (bool); } ``` - Get and Set methods in contract ```solidity /** * @notice Sets the limits config * @param maxExitRequestsLimit The maximum number of exit requests. * @param exitsPerFrame The number of exits that can be restored per frame. * @param frameDurationInSec The duration of each frame, in seconds, after which `exitsPerFrame` exits can be restored. */ function setExitRequestLimit( uint256 maxExitRequestsLimit, uint256 exitsPerFrame, uint256 frameDurationInSec ) external; /** * @notice Returns information about current limits data * @return maxExitRequestsLimit Maximum exit requests limit * @return exitsPerFrame The number of exits that can be restored per frame. * @return frameDurationInSec The duration of each frame, in seconds, after which `exitsPerFrame` exits can be restored. * @return prevExitRequestsLimit Limit left after previous requests * @return currentExitRequestsLimit Current exit requests limit */ function getExitRequestLimitFullInfo() external view returns ( uint256 maxExitRequestsLimit, uint256 exitsPerFrame, uint256 frameDurationInSec, uint256 prevExitRequestsLimit, uint256 currentExitRequestsLimit ); ``` ### Appendix B - Easy Track Factories for VEB To simplify the exit request process for Node Operators (NOs) from the **Curated** and **sDVT Staking Modules**, **Easy Track Factories** will be set up to facilitate exit requests. **Easy Track Factories** will allow authorized actors to submit a report hash to the **Validator Exit Bus (VEB)** along with a desired list of validators, applying the following sanity checks: - Verify that the validators in the list are under the actor’s control. - Verify that the validator keys genuinely belong to the specified staking module and Node Operator. - Verify that the keys were deposited through the Lido protocol. **Technical specification:** https://hackmd.io/f2ilNvRnQ1uONfVZ93wnhw?view#EVM-Script-Specification ## 7. Offchain Components ### 7.1 Validator Exit Bus Oracle The following changes will be applied to the **off-chain VEBO exit order**: - **Remove the "stuck and delayed key" predicate** – since Node Operators will no longer be able to intentionally delay exits (because of TW), this check is obsolete. - **Remove the 1% Penetration in Ethereum Stake predicate** – originally designed to prevent newly onboarded Node Operators from immediately exiting validators after receiving stake. However, this logic is currently non-functional and adds unnecessary configuration complexity. - Updated Predicate List ```python | Sorting | Module | Node Operator | Validator | | ------- | ------------------------------------------- | ----------------------------------------------------- | ---------------------- | | V | | Highest number of targeted validators to boosted exit | | | V | | Highest number of targeted validators to smooth exit | | | V | Highest deviation from the exit share limit | | | | V | | Highest number of validators | | | V | | | Lowest validator index | ``` --- **Duplicating Validator Exit Requests** After the introduction of **VEB** and the ability for other actors to deliver and unpack reports, the assumption that validators will be exited strictly in ascending order of their indexes from the CL within each Node Operator will no longer be valid. This means that smart contracts will no longer store validator indexes for each Node Operator, and **VEBO** will have to independently determine which validators have already been requested for exit. **VEBO** will rely on emitted events `ValidatorExitRequest` to decide which validators can be exited. The general logic is as follows: - Fetch all active validators from the CL. - Exclude all validators who have been requested for exit during the last time window starting from the reference slot. The size of this window is controlled by the parameter `EXIT_EVENTS_LOOKBACK_WINDOW_IN_SLOTS`, which is located in the `OracleDaemonConfigs` contract. Given the above, there’s a possible situation where **VEBO** may re-request exits for validators that were already requested for exit earlier if those requests were made a long time ago and the validators have not yet exited. This is considered a normal scenario - it’s expected that all validators will eventually be exited, either by the Node Operator or using the **TWR** framework. Additionally, if validators who were exited before the introduction of the **TW** framework end up stuck, the Oracle will re-request their exit, and they will subsequently be able to exit through the **TW** mechanism. ### 7.2 Accounting Oracle Changes to the **Accounting Oracle** contract include the complete **removal of functionality related to reporting "stuck keys"**. ### 7.3 Validator Ejector The current **whitelisting logic in the Validator Ejector** will require adjustments. With the introduction of Triggerable Withdrawals, not only Oracles but also permissionless actors may unpack reports. The original whitelisting mechanism was intended to mitigate risks from compromised nodes. **Required Changes:** - **Update Node Operator Policy** – Node Operators should configure the Ejector to work with **local Execution Layer (EL) nodes**, minimizing external trust dependencies. - **Additional Security Improvement** – Implement additional checks to verify whether the submitted report hash was delivered by a **trusted actor**. - **Logic Change** – The Validator Ejector previously relied on `lastRequestedValidatorIndex`, but there is no longer an assumption that validators exit sequentially in ascending order. Going forward, the Ejector will primarily rely on `ValidatorExitRequest` ****events. ### 7.4 Monitoring A list of alerts is currently being developed and will be implemented in a future phase. See: [RFC: TW Monitoring and Alerts](https://www.notion.so/RFC-TW-Monitoring-and-Alerts-1e4bf633d0c98059bf29cea031a6d3dd?pvs=21) ### 7.5 Trigger Exits Bot Lido requires an automated mechanism to **trigger validator exits** when delays occur, ensuring that withdrawal requests are not unnecessarily stalled and users experience smooth exits. The bot will: - Scan for validators marked as requested-to-exit in the Validator Exit Bus (VEB). - Monitor whether they have completed their exit in a timely manner. - Estimate and use optimal gas prices for efficient execution. - Operate permissionlessly—**anyone can run the bot** to help execute exits. - Depend on Staking Modules to expose clear rules or public methods for determining which validators are delayed. - Serve as a **fallback mechanism** when Node Operators fail to execute voluntary exits on time, by submitting TWRs through the EL. ### 7.6 Validator Late Prover Bot Lido also requires an automated tool to **detect and report late validators** who have failed to exit within the required timeframe after an exit request. The bot will: - Continuously track validator exit status on the CL. - Compare current status to the timestamp of the `ExitRequested` event from VEB. - Generate and submit **proofs of delinquency** if a validator has overstayed its exit window. - Operate permissionlessly, ensuring anyone can contribute to protocol safety. - Rely on Staking Modules to expose public interfaces or logic to validate the delay and apply penalties. ## 8. **Security considerations** ### 8.1 Attack Vector: Vulnerability from Uncontrolled Creation of TWRs If proper access controls or exit report validations are missing or incorrectly enforced in the **Triggerable Withdrawals flow**, a malicious actor may be able to **bypass intended safeguards** and force **unauthorized or premature exits** of active validators. This may occur under several conditions: - A **compromised or misconfigured smart contract** allows EL exits without verifying validator ownership or deposit origin. - The **Triggerable Withdrawals Gateway** fails to enforce role-based restrictions, allowing anyone to invoke `triggerExitsDirectly`. **Impact:** - Mass exit of healthy validators, negatively affecting protocol APR and TVL. - **Loss of rewards** for Node Operators due to unexpected validator exits. - **Reputational damage** and erosion of user trust. **Mitigations:** - Enforce strict access control in the Triggerable Withdrawals Gateway via role-based checks. - Limit TWRs creating. - Implement monitoring and alerting; allow the contract to be paused via the GreatSeal contract. - Add constraints to TWR creation for trusted smart contracts (e.g., validate key ownership, deposit origin — more details [here](https://www.notion.so/1eabf633d0c9801b837ec3b8e39dc01a?pvs=21)). ### 8.2 Spam attack on TW limits. Trying to censorship usefull TWR The **Triggerable Withdrawals framework** enforces a TW **limit** to protect the protocol from excessive or abusive validator exits. However this mechanism can be exploited by a malicious actor to perform a **DoS** attack by **saturating the tw quota with spam validator TWRs**, spending limit quota and thereby blocking legitimate exits during that frame. Impact: - Validators cannot be exited via the Execution Layer when needed. Mitigation: - The cost of spamming will be prohibitively high for the attacker, provided the quota limits are not set too low. Analytics research: - https://hackmd.io/5wN10bGaSbyPwpzcVkdVVw?view ## 9. Roles to actors mapping ***New and related to TW update roles only*** #### `ValidatorExitBusOracle.sol` | Role | Assignee | | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `DEFAULT_ADMIN_ROLE` | Aragon Agent | | `SUBMIT_REPORT_HASH_ROLE` | [Easy Track Motion](https://docs.lido.fi/guides/easy-track-guide/) will be used by Node Operators and sDVTComeette to eject validators | | `EXIT_REQUEST_LIMIT_MANAGER_ROLE` | Not assigned by default | | `PAUSE_ROLE` | GateSeal contract | | `RESUME_ROLE` | ResealManager contract | #### `TriggerableWithdrawalsGateway.sol` | Role | Assignee | | ---------------------------------- | ---------------------------------- | | `DEFAULT_ADMIN_ROLE` | Aragon Agent | | `ADD_FULL_WITHDRAWAL_REQUEST_ROLE` | `ValidatorsExitBusOracle` contract and `CSEjector` contract | | `EXIT_REQUEST_LIMIT_MANAGER_ROLE` | Not assigned by default | | `PAUSE_ROLE` | GateSeal contract | | `RESUME_ROLE` | ResealManager contract | #### `StakingRouter.sol` | Role | Assignee | | -------------------------------------- | ---------------------------------------- | | `DEFAULT_ADMIN_ROLE` | Aragon Agent | | `REPORT_VALIDATOR_EXITING_STATUS_ROLE` | `ValidatorExitDelayVerifier` contract | | `REPORT_VALIDATOR_EXIT_TRIGGERED_ROLE` | `TriggerableWithdrawalsGateway` contract | #### `NoderOperatorRegistry.sol` | Role | Assignee | | --------------------------- | ------------------------ | | `DEFAULT_ADMIN_ROLE` | Aragon Agent | | `MANAGE_NODE_OPERATOR_ROLE` | Easy Track Motion | | `STAKING_ROUTER_ROLE` | `StakingRouter` contract | ## 10. References - [**Pull request**](https://github.com/lidofinance/core/pull/1018/files#diff-29501cc802decb3602e6394c6438035c575680b4c9e5e7324f89a6f54f09a49d) - [**Audit scope**](https://hackmd.io/@lido/HJKEEyHbee) - [**EIP-7002: Execution-layer Triggerable Exits**](https://eips.ethereum.org/EIPS/eip-7002): The primary standard introducing a precompile contract for initiating validator exits via the Execution Layer, without requiring the validator's key. - [**EIP-7685: EL Request Interface**](https://eips.ethereum.org/EIPS/eip-7685): A general interface specification for contracts that submit and process requests to the Execution Layer (e.g., exits, consolidations, etc.). - [**CSM V2: EIP-7002 support**](https://hackmd.io/@lido/HJrMPHUt0#EIP-7002-support): Description of TW-related requirements from the Curated Staking Module (CSM), including cases involving lost validator keys or long-term performance failures. - [**Oracles technical details**](https://docs.lido.fi/guides/oracle-operator-manual/#oracle-phases): Description of how on-chain and off-chain oracles work. - [**Validators Exit Bus interface**](https://docs.lido.fi/staking-modules/csm/guides/events#contract-vebo): Current VEBO interface with events details. - [**VEBO**](https://docs.lido.fi/guides/oracle-spec/validator-exit-bus): Specification how VEBO works.

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