Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2758 +/- ##
==========================================
+ Coverage 39.02% 39.64% +0.62%
==========================================
Files 126 127 +1
Lines 5825 5948 +123
Branches 1546 1587 +41
==========================================
+ Hits 2273 2358 +85
- Misses 3550 3588 +38
Partials 2 2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Added a failConsolidation function
Verify balances in confirmConsolidation so the strategy balance is updated
restrict verifyBalance on the Compounding Staking Strategy
Added activeIds option to verifyBalances Hardhat task
More fork tests
contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol
Outdated
Show resolved
Hide resolved
Deploy script for Hoodi
sparrowDom
left a comment
There was a problem hiding this comment.
Did a part review. Left some comments proposing how we could speed up consolidations.
contracts/contracts/strategies/NativeStaking/ConsolidationController.sol
Show resolved
Hide resolved
contracts/contracts/strategies/NativeStaking/ConsolidationController.sol
Show resolved
Hide resolved
| }(sourcePubKeys, targetPubKey); | ||
|
|
||
| // Snap the balances for the last time on the new Compounding Staking Strategy | ||
| // until the consolidations are confirmed |
There was a problem hiding this comment.
nit: outdated comment. Balances can be snapped even after the consolidation has initiated. By anyone.
There was a problem hiding this comment.
any can call snapBalances, but those snaps can't be verified with verifyBalances when a consolidation is in progress. The owner needs to call confirmConsolidation which verifies the balances against a new snapped balance.
There was a problem hiding this comment.
I guess I am a little confused of the purpose of balance snapping at the time the consolidations initiate. Since 35 slots after consolidations begin (SNAP_BALANCES_DELAY) anyone could call snapBalances and invalidate the possibility of validating the balances after the time the consolidations initiate (in case our off-chain component isn't fast enough with proof generation).
Just a note here: there is also a possibility that the previous slot hasn't proposed a block. There is a small chance of that occurring, but it could make it impossible to verify the balances against the block snapped at the time of consolidation.
Is the goal of balances snapping at the time of consolidation to make sure that the state of accounting isn't too stale before the consolidations begin?
| /** | ||
| * @notice A consolidation request can fail to be processed on the beacon chain | ||
| * for various reasons. For example, the pending consolidation queue is full with 262,144 requests. | ||
| * Or the source validator has exited from a voluntary exit request. |
There was a problem hiding this comment.
Or even more likely the churn limit has been reached and the current epoch is ignoring the requests.
| */ | ||
| function requestConsolidation( | ||
| address _sourceStrategy, | ||
| bytes[] calldata sourcePubKeys, |
There was a problem hiding this comment.
Current churn limit for consolidations per epoch is Ξ304. Meaning that if no one else is requesting consolidations we will at most get 9 validators into the consolidation process. I would add a require where the transaction reverts if sourcePubKeys.length > 9
There was a problem hiding this comment.
Where does this Ξ304 limit come from?
There was a problem hiding this comment.
Explained by Grok:
The 304 ETH/epoch consolidation churn limit is derived from the base churn amount minus the capped activation/exit churn limit, calculated as follows (all values in Gwei for precision, with ETH equivalents for clarity):
- Retrieve the total active effective balance on mainnet (~36.74 million ETH, or ~36.74e15 Gwei).
- Compute the base churn as total_active_balance // CHURN_LIMIT_QUOTIENT, where CHURN_LIMIT_QUOTIENT = 65,536 (~561 ETH equivalent).
- The activation/exit churn limit is capped at MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT = 256 ETH (or 256e9 Gwei).
- The consolidation churn limit is then the base churn minus this capped activation/exit limit (~561 - 256 = 305 ETH, but precisely 304 ETH after Gwei-level floor division and minimum checks against MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA = 128 ETH).
contracts/contracts/strategies/NativeStaking/ConsolidationController.sol
Outdated
Show resolved
Hide resolved
contracts/contracts/strategies/NativeStaking/ConsolidationController.sol
Show resolved
Hide resolved
| bytes calldata targetPubKey | ||
| ) external payable onlyOwner { | ||
| // Check no consolidations are already in progress | ||
| require(consolidationCount == 0, "Consolidation in progress"); |
There was a problem hiding this comment.
After some thought I think we could do with an alternate approach. Currently the contract is designed to support consolidation process in the following manner:
- up to 9 validators can be put into the consolidation queue per transaction (actually more validators are accepted by the contract, but beacon chain will ignore those requests and the consolidations will need to be failed)
- then the contract needs to wait for 27 hours (or more) for the consolidations to finalize
- another batch of 9 validators can be initiated to be consolidated.
What it we modify the behaviour where we allow:
- multiple kick-offs of the 9 validator consolidations (which we can trigger every epoch)
- where we:
- add new ones to the sum of:
consolidationCount - leave the
consolidationStartTimestampto the one of the first request - impose a restriction where
sourceStrategyandtargetPubKeyHashof the multiple requests must match - set a hard limit of how many validators can be added in multiple requests before the consolidation is finished / balances verified
- add new ones to the sum of:
Stake to sweeping validators changed to not use KeyValueStoreClient
sparrowDom
left a comment
There was a problem hiding this comment.
Have completed the review, left some comments inline.
Happy to re-review it once / if we do the changes where more than 9 validators can enter the consolidation process.
contracts/contracts/strategies/NativeStaking/ConsolidationController.sol
Show resolved
Hide resolved
contracts/test/strategies/stakingConsolidation.mainnet.fork-test.js
Outdated
Show resolved
Hide resolved
contracts/test/strategies/stakingConsolidation.mainnet.fork-test.js
Outdated
Show resolved
Hide resolved
contracts/test/strategies/stakingConsolidation.mainnet.fork-test.js
Outdated
Show resolved
Hide resolved
contracts/test/strategies/stakingConsolidation.mainnet.fork-test.js
Outdated
Show resolved
Hide resolved
contracts/contracts/strategies/NativeStaking/CompoundingValidatorManager.sol
Show resolved
Hide resolved
|
Also very good job on the fork tests. Nice to see such thorough coverage. |
Added more fork tests
Overview
A new staking strategy was implemented in 2025 that used the compounding validators introduced in Ethereum’s Pectra release. The plan was to exit from the sweeping (0x01 type) validators used by the old Native Staking Strategy and then deposit to the new compounding (0x02 type) validators used by the new Compounding Staking Strategy.
Unfortunately, since the start of 2026, the validator deposit queue has blown own to over 70 days as at 3 February 2026. As deposit do not earn any yield, having 18.6k ETH (71.1%) of OETH’s collateral not earning yield is not acceptable. A better approach is to use the validator consolidation the was introduced in the Pectra release that reduced the lose of yield to 27 hours.
See https://www.validatorqueue.com/ for deposit queue times.
Objectives/Requirements
Link
Consolidation Processes
Contract Changes
New
ConsolidationControllercontractrequestConsolidationfunctionsourcePubKeys,targetPubKeydepositsrequestConsolidationon the old Native Staking StrategysnapBalanceson the new Compounding Staking Strategy as this will be the last time the balance can be updated until after the consolidation process has completed.confirmConsolidationfunctionbalanceProofsandpendingDepositProofsverifyBalanceson the new Compounding Staking Strategy with the balance and pending deposit proofsconfirmConsolidationon the old Native Staking Strategy with the number of validators that were consolidated.failConfirmationfunctionsourcePubKeysfailConsolidationon the old Native Staking StrategydoAccounting- can be forwarded at any timeexitSsvValidator- can be forwarded at any timeremoveSsvValidator- can only be forwarded if no consolidation is in progressvalidatorWithdrawal- full validator exits are prevented. Partial withdrawals are allowedstakeEth- deposits to a target validator of an active consolidation is prevented. Initial 1 ETH deposits to new validators and top-ups are allowed.verifyBalancescan be called by anyone and forward to the the Compounding Staking Strategy whenChanges to the old Native Staking Strategy
requestConsolidationfunctionsourcePubKeysandtargetPubKeyConsolidationControllercontractconfirmConsolidationfunctionConsolidationControllercontractactiveDepositedValidatorsby the number of validators that were consolidated. This effectively decreases the strategy’s balance fromcheckBalanceby 32 ETH times thye number of validators that were consolidated.failConsolidationfunctionConsolidationControllercontractSecurity Audit Scope
In scope contracts
ConsolidationControllerNativeStakingSSVStrategywith a focus on therequestConsolidation,failConsolidationandconfirmConsolidationfunctionsCompoundingStakingSSVStrategywith a focus onsnapBalancesandverifyBalancesOut of scope contracts
FeeAccumulatorBeaconProofsCompoundingStakingStrategyViewTests
Consolidation fork tests are in
contracts/test/strategies/stakingConsolidation.mainnet.fork-test.jsDeployment
Deployment script is in
contracts/deploy/mainnet/173_staking_consolidation.jsCode Change Checklist
To be completed before internal review begins:
Internal review: