Skip to content

Staking router 3.0#1436

Draft
Amuhar wants to merge 530 commits intodevelopfrom
feat/staking-router-3.0
Draft

Staking router 3.0#1436
Amuhar wants to merge 530 commits intodevelopfrom
feat/staking-router-3.0

Conversation

@Amuhar
Copy link
Copy Markdown
Contributor

@Amuhar Amuhar commented Sep 11, 2025

❗ This is a draft pull request

  • Support for 0x01 and 0x02 keys
  • Consolidation
  • Top-ups 0x02 keys

Not ready for review

@Amuhar Amuhar added the valset Updates from the ValSet Tech team label Sep 11, 2025
@github-actions
Copy link
Copy Markdown

github-actions bot commented Sep 11, 2025

badge

Hardhat Unit Tests Coverage Summary

Details
Filename                                                                Stmts    Miss  Cover    Missing
--------------------------------------------------------------------  -------  ------  -------  -----------------------------------------------------------------------------------------------------------
contracts/0.4.24/Lido.sol                                                 305      11  96.39%   1024-1043, 1151-1163
contracts/0.4.24/StETH.sol                                                 80       0  100.00%
contracts/0.4.24/StETHPermit.sol                                           15       0  100.00%
contracts/0.4.24/lib/Packed64x4.sol                                         5       0  100.00%
contracts/0.4.24/lib/SigningKeys.sol                                       36       0  100.00%
contracts/0.4.24/lib/StakeLimitUtils.sol                                   41       0  100.00%
contracts/0.4.24/nos/NodeOperatorsRegistry.sol                            435       0  100.00%
contracts/0.4.24/utils/Pausable.sol                                         9       0  100.00%
contracts/0.4.24/utils/UnstructuredStorageExt.sol                          14       0  100.00%
contracts/0.4.24/utils/Versioned.sol                                        5       0  100.00%
contracts/0.6.12/WstETH.sol                                                17       0  100.00%
contracts/0.8.25/CLValidatorVerifier.sol                                   34       1  97.06%   92
contracts/0.8.25/TopUpGateway.sol                                          98       2  97.96%   233, 281
contracts/0.8.25/ValidatorExitDelayVerifier.sol                            75       0  100.00%
contracts/0.8.25/consolidation/ConsolidationBus.sol                        75       0  100.00%
contracts/0.8.25/consolidation/ConsolidationGateway.sol                    75       0  100.00%
contracts/0.8.25/consolidation/ConsolidationMigrator.sol                   65       0  100.00%
contracts/0.8.25/lib/BeaconChainDepositor.sol                              40       4  90.00%   44, 47, 82, 97
contracts/0.8.25/sr/ISRBase.sol                                             0       0  100.00%
contracts/0.8.25/sr/SRLib.sol                                             289      16  94.46%   57, 95-144, 312
contracts/0.8.25/sr/SRStorage.sol                                          13       0  100.00%
contracts/0.8.25/sr/SRTypes.sol                                             0       0  100.00%
contracts/0.8.25/sr/SRUtils.sol                                            13       1  92.31%   87
contracts/0.8.25/sr/StakingRouter.sol                                     263      14  94.68%   70, 374-383, 628-629, 651, 707, 711, 759, 857-862, 1133
contracts/0.8.25/utils/AccessControlConfirmable.sol                         2       0  100.00%
contracts/0.8.25/utils/Confirmable2Addresses.sol                            5       0  100.00%
contracts/0.8.25/utils/Confirmations.sol                                   37       0  100.00%
contracts/0.8.25/utils/PausableUntilWithRoles.sol                           3       0  100.00%
contracts/0.8.25/vaults/LazyOracle.sol                                    134      18  86.57%   203-209, 248, 276-279, 436, 449, 467, 515, 556-558, 650, 658
contracts/0.8.25/vaults/OperatorGrid.sol                                  196       1  99.49%   203
contracts/0.8.25/vaults/PinnedBeaconProxy.sol                               6       0  100.00%
contracts/0.8.25/vaults/StakingVault.sol                                  111      14  87.39%   307-341
contracts/0.8.25/vaults/ValidatorConsolidationRequests.sol                 48       3  93.75%   183, 187, 199
contracts/0.8.25/vaults/VaultFactory.sol                                   34       0  100.00%
contracts/0.8.25/vaults/VaultHub.sol                                      425      76  82.12%   257-266, 281-287, 342-366, 383, 552-553, 595-688, 997-999, 1087-1091, 1147, 1202-1209, 1495-1496, 1511-1521
contracts/0.8.25/vaults/dashboard/Dashboard.sol                           137       8  94.16%   183-201, 327, 636-649
contracts/0.8.25/vaults/dashboard/NodeOperatorFee.sol                      70       0  100.00%
contracts/0.8.25/vaults/dashboard/Permissions.sol                          47       2  95.74%   321-330
contracts/0.8.25/vaults/interfaces/IPinnedBeaconProxy.sol                   0       0  100.00%
contracts/0.8.25/vaults/interfaces/IPredepositGuarantee.sol                 0       0  100.00%
contracts/0.8.25/vaults/interfaces/IStakingVault.sol                        0       0  100.00%
contracts/0.8.25/vaults/interfaces/IVaultFactory.sol                        0       0  100.00%
contracts/0.8.25/vaults/lib/PinnedBeaconUtils.sol                           5       0  100.00%
contracts/0.8.25/vaults/lib/RecoverTokens.sol                               5       0  100.00%
contracts/0.8.25/vaults/lib/RefSlotCache.sol                               36       0  100.00%
contracts/0.8.25/vaults/predeposit_guarantee/CLProofVerifier.sol           16       0  100.00%
contracts/0.8.25/vaults/predeposit_guarantee/MeIfNobodyElse.sol             3       0  100.00%
contracts/0.8.25/vaults/predeposit_guarantee/PredepositGuarantee.sol      213      12  94.37%   483-503, 532, 671, 678, 700
contracts/0.8.9/Accounting.sol                                             94       2  97.87%   365-366
contracts/0.8.9/Burner.sol                                                 92       0  100.00%
contracts/0.8.9/DepositSecurityModule.sol                                 126       0  100.00%
contracts/0.8.9/EIP712StETH.sol                                            16       0  100.00%
contracts/0.8.9/LidoExecutionLayerRewardsVault.sol                         16       0  100.00%
contracts/0.8.9/LidoLocator.sol                                            28       0  100.00%
contracts/0.8.9/OracleDaemonConfig.sol                                     28       0  100.00%
contracts/0.8.9/TokenRateNotifier.sol                                      36      36  0.00%    35-130
contracts/0.8.9/TriggerableWithdrawalsGateway.sol                          54       1  98.15%   271
contracts/0.8.9/WithdrawalQueue.sol                                        88       0  100.00%
contracts/0.8.9/WithdrawalQueueBase.sol                                   146       0  100.00%
contracts/0.8.9/WithdrawalQueueERC721.sol                                  89       0  100.00%
contracts/0.8.9/WithdrawalVault.sol                                        37       0  100.00%
contracts/0.8.9/WithdrawalVaultEIP7685.sol                                 45       0  100.00%
contracts/0.8.9/lib/ExitLimitUtils.sol                                     35       0  100.00%
contracts/0.8.9/lib/Math.sol                                                4       0  100.00%
contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol                         22       0  100.00%
contracts/0.8.9/lib/UnstructuredRefStorage.sol                              2       0  100.00%
contracts/0.8.9/oracle/AccountingOracle.sol                               197       3  98.48%   441-442, 616
contracts/0.8.9/oracle/BaseOracle.sol                                      89       1  98.88%   401
contracts/0.8.9/oracle/HashConsensus.sol                                  263       1  99.62%   1005
contracts/0.8.9/oracle/ValidatorsExitBus.sol                              240       0  100.00%
contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol                         57       1  98.25%   217
contracts/0.8.9/proxy/OssifiableProxy.sol                                  17       0  100.00%
contracts/0.8.9/proxy/WithdrawalsManagerProxy.sol                          60       0  100.00%
contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol               377       2  99.47%   1288, 1300
contracts/0.8.9/utils/DummyEmptyContract.sol                                0       0  100.00%
contracts/0.8.9/utils/PausableUntil.sol                                    31       0  100.00%
contracts/0.8.9/utils/Versioned.sol                                        11       0  100.00%
contracts/0.8.9/utils/access/AccessControl.sol                             23       0  100.00%
contracts/0.8.9/utils/access/AccessControlEnumerable.sol                    9       0  100.00%
contracts/common/utils/PausableUntil.sol                                   29       0  100.00%
contracts/tooling/AlertingHarness.sol                                      54       1  98.15%   97
contracts/tooling/sepolia/SepoliaDepositAdapter.sol                        21      21  0.00%    55-106
TOTAL                                                                    5971     252  95.78%

Diff against master

Filename                                                            Stmts    Miss  Cover
----------------------------------------------------------------  -------  ------  --------
contracts/0.4.24/Lido.sol                                             +24       0  +0.30%
contracts/0.8.25/CLValidatorVerifier.sol                              +34      +1  +97.06%
contracts/0.8.25/TopUpGateway.sol                                     +98      +2  +97.96%
contracts/0.8.25/consolidation/ConsolidationBus.sol                   +75       0  +100.00%
contracts/0.8.25/consolidation/ConsolidationGateway.sol               +75       0  +100.00%
contracts/0.8.25/consolidation/ConsolidationMigrator.sol              +65       0  +100.00%
contracts/0.8.25/lib/BeaconChainDepositor.sol                         +40      +4  +90.00%
contracts/0.8.25/sr/ISRBase.sol                                         0       0  +100.00%
contracts/0.8.25/sr/SRLib.sol                                        +289     +16  +94.46%
contracts/0.8.25/sr/SRStorage.sol                                     +13       0  +100.00%
contracts/0.8.25/sr/SRTypes.sol                                         0       0  +100.00%
contracts/0.8.25/sr/SRUtils.sol                                       +13      +1  +92.31%
contracts/0.8.25/sr/StakingRouter.sol                                +263     +14  +94.68%
contracts/0.8.25/vaults/predeposit_guarantee/CLProofVerifier.sol        0      -1  +6.25%
contracts/0.8.9/Accounting.sol                                         -2       0  -0.05%
contracts/0.8.9/DepositSecurityModule.sol                              -2       0  +100.00%
contracts/0.8.9/LidoLocator.sol                                        +2       0  +100.00%
contracts/0.8.9/WithdrawalVault.sol                                    +5       0  +100.00%
contracts/0.8.9/WithdrawalVaultEIP7685.sol                            +45       0  +100.00%
contracts/0.8.9/oracle/AccountingOracle.sol                           +23      +3  -1.52%
contracts/0.8.9/oracle/ValidatorsExitBus.sol                         +102     -10  +7.25%
contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol                     +5       0  +0.17%
contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol          +145     -10  +4.64%
TOTAL                                                               +1312     +20  +0.45%

Results for commit: b0c3278

Minimum allowed coverage is 95%

♻️ This comment has been updated with latest results

@tamtamchik tamtamchik added the solidity Smart contract code changes label Sep 13, 2025
Copy link
Copy Markdown

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slither found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@Amuhar Amuhar changed the base branch from feat/testnet-2 to feat/vaults September 29, 2025 17:13
Base automatically changed from feat/vaults to develop December 2, 2025 22:08
Comment on lines +177 to +188
function allowPair(
uint256 sourceOperatorId,
uint256 targetOperatorId,
address submitter
) external onlyRole(ALLOW_PAIR_ROLE) {
if (submitter == address(0)) revert ZeroArgument("submitter");

_allowedPairs[sourceOperatorId].add(targetOperatorId);
_submitters[sourceOperatorId][targetOperatorId] = submitter;

emit ConsolidationPairAllowed(sourceOperatorId, targetOperatorId, submitter);
}

Check warning

Code scanning / Slither

Unused return Medium

Comment on lines +118 to +122
modifier preservesEthBalance() {
uint256 balanceBeforeCall = address(this).balance - msg.value;
_;
assert(address(this).balance == balanceBeforeCall);
}

Check warning

Code scanning / Slither

Dangerous strict equalities Medium

mkurayan and others added 19 commits March 25, 2026 11:58
…solidation-flow-improvments

Feat/staking router 3.0 consolidation flow improvments
…fee-invariant

feat: enforce consistent modules fee
…ation-flow

refactor: sr modules data validation flow
…king-router-3.0-consolidation-group-requests
Comment on lines +294 to +307
function finalizeUpgrade_v4() external {
require(hasInitialized(), "NOT_INITIALIZED");
_checkContractVersion(2);
_setContractVersion(3);

_migrateStorage_v2_to_v3();

_migrateBurner_v2_to_v3(_oldBurner, _contractsWithBurnerAllowances);

_setMaxExternalRatioBP(_initialMaxExternalRatioBP);
}

function _migrateStorage_v2_to_v3() internal {
// migrate storage to packed representation
bytes32 LIDO_LOCATOR_POSITION = keccak256("lido.Lido.lidoLocator");
address locator = LIDO_LOCATOR_POSITION.getStorageAddress();
assert(locator != address(0)); // sanity check

_setLidoLocator(LIDO_LOCATOR_POSITION.getStorageAddress());
LIDO_LOCATOR_POSITION.setStorageUint256(0);

bytes32 BUFFERED_ETHER_POSITION = keccak256("lido.Lido.bufferedEther");
_setBufferedEther(BUFFERED_ETHER_POSITION.getStorageUint256());
BUFFERED_ETHER_POSITION.setStorageUint256(0);

bytes32 DEPOSITED_VALIDATORS_POSITION = keccak256("lido.Lido.depositedValidators");
_setDepositedValidators(DEPOSITED_VALIDATORS_POSITION.getStorageUint256());
DEPOSITED_VALIDATORS_POSITION.setStorageUint256(0);

bytes32 CL_VALIDATORS_POSITION = keccak256("lido.Lido.beaconValidators");
bytes32 CL_BALANCE_POSITION = keccak256("lido.Lido.beaconBalance");
_setClBalanceAndClValidators(
CL_BALANCE_POSITION.getStorageUint256(),
CL_VALIDATORS_POSITION.getStorageUint256()
);
CL_BALANCE_POSITION.setStorageUint256(0);
CL_VALIDATORS_POSITION.setStorageUint256(0);

bytes32 TOTAL_SHARES_POSITION = keccak256("lido.StETH.totalShares");
uint256 totalShares = TOTAL_SHARES_POSITION.getStorageUint256();
assert(totalShares > 0); // sanity check
TOTAL_AND_EXTERNAL_SHARES_POSITION.setLowUint128(totalShares);
TOTAL_SHARES_POSITION.setStorageUint256(0);
}

function _migrateBurner_v2_to_v3(
address _oldBurner,
address[] _contractsWithBurnerAllowances
) internal {
require(_oldBurner != address(0), "OLD_BURNER_ADDRESS_ZERO");
address burner = _burner();
require(_oldBurner != burner, "OLD_BURNER_SAME_AS_NEW");

// migrate burner stETH balance
uint256 oldBurnerShares = _sharesOf(_oldBurner);
if (oldBurnerShares > 0) {
_transferShares(_oldBurner, burner, oldBurnerShares);
_emitTransferEvents(_oldBurner, burner, getPooledEthByShares(oldBurnerShares), oldBurnerShares);
}

// initialize new burner with state from the old burner
IBurnerMigration(burner).migrate(_oldBurner);

// migrating allowances
for (uint256 i = 0; i < _contractsWithBurnerAllowances.length; i++) {
uint256 oldAllowance = allowance(_contractsWithBurnerAllowances[i], _oldBurner);
_approve(_contractsWithBurnerAllowances[i], _oldBurner, 0);
_approve(_contractsWithBurnerAllowances[i], burner, oldAllowance);
}
/// @dev prevent migration if the last oracle report wasn't submitted, otherwise deposits
/// made after refSlot and before migration (i.e. report's tx) will be lost
IAccountingOracle oracle = _accountingOracle();
(,,, bool mainDataSubmitted,,,,,) = oracle.getProcessingState();
/// @dev pass in case of initial deploy
require(mainDataSubmitted || oracle.getLastProcessingRefSlot() == 0, "NO_REPORT");

_checkContractVersion(3);
_setContractVersion(4);
_migrateStorage_v3_to_v4();
}

Check warning

Code scanning / Slither

Unused return Medium

…solidation-group-requests

feat: wrap consolidation requests to struct
Comment on lines +185 to +223
function addConsolidationRequests(
ConsolidationWitnessGroup[] calldata groups,
address refundRecipient
) external payable onlyRole(ADD_CONSOLIDATION_REQUEST_ROLE) preservesEthBalance whenResumed {
if (msg.value == 0) revert ZeroArgument("msg.value");
uint256 groupsCount = groups.length;
if (groupsCount == 0) revert ZeroArgument("groups");

// Count total individual requests across all groups
uint256 requestsCount = 0;
for (uint256 i = 0; i < groupsCount; ++i) {
uint256 groupSize = groups[i].sourcePubkeys.length;
if (groupSize == 0) revert EmptyGroup(i);
requestsCount += groupSize;
}

_checkConsolidationPreconditions();

(IWithdrawalVault withdrawalVault, bytes32 withdrawalCredentials) = _getWithdrawalVaultData();

for (uint256 i = 0; i < groupsCount; ++i) {
_validatePubKeyWCProof(groups[i].targetWitness, withdrawalCredentials);
}

_consumeConsolidationRequestLimit(requestsCount);

uint256 fee = withdrawalVault.getConsolidationRequestFee();
uint256 totalFee = requestsCount * fee;
uint256 refund = _checkFee(totalFee);

// Expand grouped requests into flat pairs for WithdrawalVault
(bytes[] memory sourcePubkeys, bytes[] memory targetPubkeys) = _prepareConsolidationPairs(
groups,
requestsCount
);
withdrawalVault.addConsolidationRequests{value: totalFee}(sourcePubkeys, targetPubkeys);

_refundFee(refund, refundRecipient);
}

Check failure

Code scanning / Slither

Functions that send Ether to arbitrary destinations High

Comment on lines +413 to +418
function _getDepositedValidatorsCount(
IUnifiedStakingModule module,
uint256 operatorId
) internal view returns (uint256 totalDeposited) {
(, , , , , , totalDeposited, ) = module.getNodeOperatorSummary(operatorId);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

solidity Smart contract code changes valset Updates from the ValSet Tech team

Projects

None yet

Development

Successfully merging this pull request may close these issues.