EIP-7002 fixes the principal-agent problem in delegated staking—where stakers must trust validator node operators to pre-sign exit messages, or honor future exit requests—by introducing a new voluntary exit operation that can be triggered with a validator’s withdrawal credential. This empowers stakers to withdraw staked ETH without relying on the entity holding the validator’s signing key (i.e., the staking service in a delegated staking setup) to process withdrawals.
There are four main reasons Lido protocol should support Triggerable Withdrawals (TW):
EIP-7685 describes general framework for storing contract-triggered requests. When developing a Withdrawal Credentials contract, it is important to consider that other types of requests may appear in the future (like consolidation in EIP-7251).
EIP-7002 key feature is the introduction of a stateful validator exit precompile that maintains a queue of validator exit messages originating from the execution layer.
Exit messages are regular Ethereum transactions with the validator precompile address as the target and indicate intent to exit a validator (identified by its public key). A validator exit message is valid if (a) it is signed by the Ethereum address referenced in the validator’s execution-layer (0x01) withdrawal credential (b) the validator to be exited is active on the Beacon Chain. These checks are executed by the consensus layer after the exit message makes its way to the Beacon Chain; the validator exit precompile only confirms if an exit transaction pays enough fee at the time the exit precompile is called by a withdrawal credential address.
Dual Governance allows stETH holders, in the worst case, to delay any proposed changes to the Lido protocol until they have fully exited (withdrawn their ETH from the protocol). Currently, the withdrawal process depends on the AO report (finalizing withdrawal requests), VEBO (requesting to exit a sufficient number of validators), and NOs (sending the exit message). By making the triggerable flow permissionless, we can eliminate the influence of VEBO and NOs, making withdrawals more secure and trustless.
CSM is a permissionless module that compensates for reduced trust assumptions in delegated staking via bonds. There are several cases when CSM might require usage of the EL triggerable exits:
Lido governance should be able to do anything, unless it doesn't break DualGov assumptions.
To understand the following section, it is recommended to familiarize yourself with the operation of VEBO Oracles, particularly the process of submitting and unpacking hashes (Oracle phases).
Globally, the scope of changes will be as follows:
The following parts will be affected:
VEB - Validator Exit Bus Contract. Contains the following logic:
VEBO - Validator Exit Bus Oracle Contract. Contains next logic:
Delinqued key/validator - a key/validator that was unable to exit on time. Delinquent keys do not lose their status even if they are eventually exited afterward.
Events emiting in VEBO takes two steps:
The first phase involves submitting and saving the hash in the contract.
The first phase of the Oracle process won't change. Oracles deliver report hashes to the Hash Consensus contract. When a quorum is reached, the report hash is delivered to VEBO via submitReport
method. The report hash from Oracles can only be used to emit events by the Oracles themselves.
function submitReport(uint256 slot, bytes32 report, uint256 consensusVersion) external;
Trusted entities submit a report hash to the VEB contract via the submitReportHash
method. This hash is instantly saved to the VEB (in the reportHashes
mapping) and can be used by anyone to deliver report data during the second phase.
function submitReportHash(bytes32 reportHash) external;
reportHashes
mapping will have next signature:
struct DeliverHistory {
// Block number of deliver transaction
uint32 blockNumber;
// Last index of the key for which the exit event was emitted
uint32 lastDeliveredKeyIndex;
};
struct ReportStatus {
// Total items count in report (on creation 0)
uint32 totalItemsCount;
// Total processed items in report (by default 0)
uint32 deliveredItemsCount;
// Contract version on the moment of creation
uint32 contractVersion;
// Data delivery history
DeliveryHistory[] deliverHistory;
};
mapping (bytes32 => ReportStatus) public reportHashes;
The ReportStatus
structure includes a contractVersion field, indicating the contract version at the time the hash was submitted. This field is required to prevent the delivery of report data if the report hash was submitted under a previous contract version, thereby avoiding unpredictable behavior in the VEB.
// contract VEBO
function submitReportData(ReportData calldata data, uint256 contractVersion)
The Oracle unpacks the full report and emits events for all items in the report. Oracle exit limits are calculated dynamically and depend on the number of events emitted in the reference frame (details). During this phase, the ReportStatus
record is added to the reportHashes
mapping.
function emitExitEvents(ReportData calldata data, uint256 contractVersion)
Anyone can emit exit events by providing report data, the hash of which is stored in the VEB. If reportsHashes.totalItemsCount
is 0
update and save the report length. Starting from deliveredItemsCount
index, emit events for keys from the report. Once all keys are processed or the number of exits in the current frame reaches the exit limits from the OracleSanityCheck
contract, stop emitting events and save last proceed key index + 1
into deliveredItemsCount
.
Assumptions:
contractVersion
;For security reasons, the Lido protocol cannot create more exit requests in a single frame than specified in the OracleReportSanityChecker
contract. This means that keys presented in the report but lacking an exit event should not be used as indicators to exit a validator and cannot be used to exit it via a TW.
Each frame has an exit quota that cannot be exceeded. Therefore, the sum of emitted exit events in a frame and the Oracle's report for that frame must not exceed this quota.
The Offchain Oracle's report is considered the final report for the frame it is based on, even if the transaction is included in the next frame.
For next examples assume that:
In the next example, Frame 2 contains two deliver report transactions:
report #1
, so #1 was fully delivered (5 exit quota left);report #2
, it were partially delivered, because was limited by exit quota (0 exit quota left);In Frame 3
, exit events were emitted for all remaining keys from report #2
. The Oracle can report up to 5 keys for exit.
The main operations involving validators are as follows:
Once a validator is marked for exit in the VEB, the Node Operator (NO) should send an exit message on the CL. Simultaneously, anyone can trigger the withdrawal on the EL.
Anyone can provide proof that a validator did not exit on time, and this information will be relayed to the Staking Module. All further decisions regarding penalties are made by the Staking Module.
Exits using voluntary messages on the CL remain our primary method for withdrawing validators. NO hosts validator ejector that sending exit message as soon as it sees event in VEB contract.
The following function verifies that the hash of the provided report data exists in the mapping and that events for the keys have been emitted. It then triggers exits on the EL via the Withdrawal Vault contract.
function triggerExits(ReportData calldata data, uint32[] keyIndexes,)
This function contains next steps:
reportHash
mapping;Delinquent keys are keys for which validators did not start exiting before the end of the voluntary exit period. Delinquent keys can be permissionlessly reported to the VEB contract with corresponding proof that the required time has passed.
Those keys should be penalized by Staking Modules. Our current penalties concept is specific for permissionfull staking modules.
Anyone can deliver proof that validator was delayed:
function submitProofOfDelinquent(ReportData calldata data, Witness[] witness, BeaconBlock beconBlock) external;
This method will support batch proof delivery to optimize gas costs.
After receiving and verifying the proof, the VEB notifies the Staking Router about the delinquent validators. This data is then provided to the Staking Modules.
Essentially, the module notification consists of the validator's identifier within the Lido protocol, the time when its exit was requested, and the time it remained active. Staking modules use the time difference between the exit event and the timestamp when the validator was not in an exiting status to determine whether the node operator should be penalized.
Staking Modules should take into account that some validators could be not able to exit because of CL restrictions.
struct DelinquentStatus {
// Validators indetification
uint32 stakingModuleId;
uint32 nodeOperatorId;
uint32 keyIndex;
// Timestamp when validator was activated
uint32 activatedTs;
// Timestamp when exit event was emited
uint32 exitedTs;
// Timestamp of block on which validator were still active
uint32 activeTs;
}
Trusted entities will be able to directly emit exit events and request validators through the TW to exit them without delivering hashes and any proving.
This type of TW should also comply with the sanity report limits.
function triggerExitsDirectly(ValidatorsData calldata data) external;
The flow is as follows:
Since stuck keys are deprecated, all functionality related to this feature should be removed from AO.
Oralce will fetch exit events only for the last 28 days to track which validators have already been requested for exit. If this period has already passed, it is possible that the same validator might be requested to exit again. However, this scenario is unlikely because in case of stuck validators they will be exited using TW.
A bot that will generate proof that a validator was delinquent and deliver it to the protocol.
An analog of the CSM Prover tool.
The Exit Trigger bot should be accessible for anyone to use to exit validators. It should feature a straightforward interface that allows for flexible exiting of any validators.
Goals:
Generally, what is this protection for:
If a Node Operator (NO) relies on third-party providers for the ejector, these providers could potentially return falsified data, such as fake events. To address this security risk, the ejector verifies that the signatures associated with these events were created using Oracle accounts.
For example, if a malicious provider generates 10 fake events, they will be ignored by the ejector because they would be signed with a non-Oracle private key.
After TE, emitting exit events can be performed by anyone since it is permissionless, and transaction signature verification cannot be done.
To mitigate this security issue, all Node Operators (NOs) should run their ejectors on their own local nodes.