Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
c51db01
WIP validator consolidation
naddison36 Jan 19, 2026
45af6b3
Fixes
naddison36 Jan 19, 2026
35aa5e1
Fixed unit tests
naddison36 Jan 20, 2026
aa658ff
Added deploy script for consolidation
naddison36 Jan 20, 2026
6732e8d
Fix the Native Staking Strategy fork tests for the second SSV cluster
naddison36 Jan 20, 2026
8133367
Cleanup
naddison36 Jan 20, 2026
fc93cb3
Made ConsolidationController Ownable
naddison36 Jan 21, 2026
f2e42ec
WIP removing target strategy balance check to confirm consolidation
naddison36 Jan 23, 2026
79c98c4
All validatorWithdrawal for partial withdrawals
naddison36 Jan 23, 2026
8081673
Changed to owner calling consolidation functions
naddison36 Jan 27, 2026
d5c8d14
Fixed pending consolidation queue length
naddison36 Jan 27, 2026
2bcd1ec
Allow stakeEth if not depositing to a target of a consolidation
naddison36 Jan 27, 2026
857d225
Linter
naddison36 Jan 27, 2026
9753e29
Allow anyone to call snapBalances
naddison36 Jan 27, 2026
4946d2f
Merge remote-tracking branch 'origin/master' into nicka/consolidation
naddison36 Jan 27, 2026
d5b1512
Fix deploy script
naddison36 Jan 27, 2026
a5144b5
Fixed verifyBalances logic in ConsolidationController
naddison36 Jan 29, 2026
3572057
Made requestConsolidation payable and pass on the msg.value
naddison36 Jan 29, 2026
d4f6516
WIP consolidation fork tests
naddison36 Jan 29, 2026
660f096
Fix verifyBalances activeIds param default
naddison36 Jan 30, 2026
e165e09
Fixed storing the updated validator state
naddison36 Jan 30, 2026
23a6b37
requestConsolidation fork tests
naddison36 Jan 30, 2026
0d8f18b
More consolidation fork tests
naddison36 Jan 30, 2026
2d38510
More failed consolidation fork tests
naddison36 Jan 30, 2026
a563012
Fix failConsolidation when reset
naddison36 Jan 30, 2026
21848ce
More fork tests
naddison36 Jan 30, 2026
afde0de
Fixes to confirmConsolidation
naddison36 Jan 30, 2026
66715ac
confirmConsolidation fork test
naddison36 Jan 30, 2026
5dc5ed8
Updated verifyBalances HH task to override balances
naddison36 Jan 30, 2026
8d148dc
Changed amoStrat Hardhat task to use OETH/WETH Curve pool
naddison36 Feb 2, 2026
4d5b0a3
Added more fork tests
naddison36 Feb 2, 2026
93fa8dc
Added more fork tests
naddison36 Feb 2, 2026
752c3fe
Slither fixes
naddison36 Feb 2, 2026
7914c08
Update consolidation process diagram
naddison36 Feb 3, 2026
fefe967
Natspec cleanup
naddison36 Feb 3, 2026
cfa37c5
Added more consolidation fork tests
naddison36 Feb 3, 2026
dfdc548
Changed to ConsolidationController for Hoodi deployment
naddison36 Feb 5, 2026
c9e7331
Deployment to Hoodi
naddison36 Feb 5, 2026
b567597
added requestConsol Hardhat task
naddison36 Feb 5, 2026
926f34b
Added failConsol and confirmConsol HH tasks
naddison36 Feb 5, 2026
b22eb7e
Bumped Lodestar version
naddison36 Feb 5, 2026
f8da4d4
Added consol option to verifyBalances
naddison36 Feb 5, 2026
16b083b
Fixed failConsolidation
naddison36 Feb 5, 2026
787bf0b
Added check of pub key length in _hashPubKey
naddison36 Feb 5, 2026
e2bd274
Check source pub key lengths in confirm and fail consolidations
naddison36 Feb 6, 2026
493a8a5
Fixed "Source not withdrawable" check
naddison36 Feb 6, 2026
9dde901
setRegistrator HH task to work against old and new staking strategies
naddison36 Feb 9, 2026
4e03dfe
Added setStakingMonitor
naddison36 Feb 9, 2026
00735ae
Fixed typo in fork test
naddison36 Feb 10, 2026
160f67f
Fixed typos in fork tests
naddison36 Feb 10, 2026
0692950
Fixed fork test description
naddison36 Feb 10, 2026
0185f07
Restricted snapBalances when consolidation is in progress
naddison36 Feb 10, 2026
aa61519
Hoodi upgrade script
naddison36 Feb 10, 2026
0bbfc8c
Merge remote-tracking branch 'origin/master' into nicka/consolidation
naddison36 Feb 11, 2026
39dfdeb
Bumped the deploy script number
naddison36 Feb 11, 2026
2c1e237
New Hoodi deployment
naddison36 Feb 12, 2026
5419da0
Add support for multiple validator consolidation requests
naddison36 Feb 12, 2026
0b54ddf
Added limit of 9 validators per consolidation request
naddison36 Feb 13, 2026
49c8b11
Fix linter
naddison36 Feb 13, 2026
90188b4
Merge remote-tracking branch 'origin/master' into nicka/consolidation
naddison36 Feb 13, 2026
bb649cf
Bumped deploy number
naddison36 Feb 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions contracts/contracts/interfaces/IConsolidation.sol

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,15 @@ abstract contract CompoundingValidatorManager is Governable, Pausable {

/// @dev Throws if called by any account other than the Registrator
modifier onlyRegistrator() {
require(msg.sender == validatorRegistrator, "Not Registrator");
_onlyRegistrator();
_;
}

/// @dev internal function used to reduce contract size
function _onlyRegistrator() internal view {
require(msg.sender == validatorRegistrator, "Not Registrator");
}

/// @dev Throws if called by any account other than the Registrator or Governor
modifier onlyRegistratorOrGovernor() {
require(
Expand Down Expand Up @@ -952,7 +957,7 @@ abstract contract CompoundingValidatorManager is Governable, Pausable {
/// This behaviour is correct.
///
/// The validator balances on the beacon chain can then be proved with `verifyBalances`.
function snapBalances() external {
function snapBalances() external onlyRegistrator {
uint64 currentTimestamp = SafeCast.toUint64(block.timestamp);
require(
snappedBalance.timestamp + SNAP_BALANCES_DELAY < currentTimestamp,
Expand Down Expand Up @@ -1013,7 +1018,7 @@ abstract contract CompoundingValidatorManager is Governable, Pausable {
function verifyBalances(
BalanceProofs calldata balanceProofs,
PendingDepositProofs calldata pendingDepositProofs
) external {
) external onlyRegistrator {
// Load previously snapped balances for the given block root
Balances memory balancesMem = snappedBalance;
// Check the balances are the latest
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IDepositContract } from "../../interfaces/IDepositContract.sol";
import { IVault } from "../../interfaces/IVault.sol";
import { IWETH9 } from "../../interfaces/IWETH9.sol";
import { ISSVNetwork, Cluster } from "../../interfaces/ISSVNetwork.sol";
import { BeaconConsolidation } from "../../beacon/BeaconConsolidation.sol";

struct ValidatorStakeData {
bytes pubkey;
Expand Down Expand Up @@ -80,6 +81,19 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
bytes pubKey,
uint64[] operatorIds
);
event ConsolidationRequested(
bytes[] sourcePubKeys,
bytes targetPubKey,
uint256 consolidationCount
);
event ConsolidationFailed(
bytes[] sourcePubKeys,
uint256 consolidationCount
);
event ConsolidationConfirmed(
uint256 consolidationCount,
uint256 activeDepositedValidators
);
event StakeETHThresholdChanged(uint256 amount);
event StakeETHTallyReset();

Expand Down Expand Up @@ -360,6 +374,103 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
ISSVNetwork(SSV_NETWORK).withdraw(operatorIds, ssvAmount, cluster);
}

/***************************************
Consolidation functions
****************************************/

/**
* @notice Initiates the consolidation of multiple source sweeping validators to a single compounding validator.
* @dev The validator registrator should be set to the ConsolidationController contract which
* has checks against the target validator.
* @param sourcePubKeys The full public keys of the source validators to be consolidated.
* @param targetPubKey The full public key of the target validator to consolidate into.
*/
function requestConsolidation(
bytes[] calldata sourcePubKeys,
bytes calldata targetPubKey
) external payable nonReentrant whenNotPaused onlyRegistrator {
// Hash using the Native Staking Strategy's hashing method.
// This is different to the Beacon chain's method.
bytes32 targetPubKeyHash = keccak256(targetPubKey);
bytes32 sourcePubKeyHash;

// For each source validator
for (uint256 i = 0; i < sourcePubKeys.length; ++i) {
sourcePubKeyHash = keccak256(sourcePubKeys[i]);
require(sourcePubKeys[i].length == 48, "Invalid source public key");
require(sourcePubKeyHash != targetPubKeyHash, "Self consolidation");
require(
validatorsStates[sourcePubKeyHash] == VALIDATOR_STATE.STAKED,
"Source validator not staked"
);

// Store the state of the source validator as exiting so it can be removed
// after the consolidation is confirmed
validatorsStates[sourcePubKeyHash] = VALIDATOR_STATE.EXITING;

// Request consolidation from source to target validator
BeaconConsolidation.request(sourcePubKeys[i], targetPubKey);
}

emit ConsolidationRequested(
sourcePubKeys,
targetPubKey,
sourcePubKeys.length
);
}

/**
* @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.
* This restores the validator states back to STAKED so they can be consolidated again or exited.
* @param sourcePubKeys The full public keys of the source validators that failed to be consolidated.
*/
function failConsolidation(bytes[] calldata sourcePubKeys)
external
nonReentrant
whenNotPaused
onlyRegistrator
{
bytes32 sourcePubKeyHash;

// For each failed source validator
for (uint256 i = 0; i < sourcePubKeys.length; ++i) {
require(sourcePubKeys[i].length == 48, "Invalid source public key");
sourcePubKeyHash = keccak256(sourcePubKeys[i]);
require(
validatorsStates[sourcePubKeyHash] == VALIDATOR_STATE.EXITING,
"Source validator not exiting"
);

// Store the state of the source validator back to staked
validatorsStates[sourcePubKeyHash] = VALIDATOR_STATE.STAKED;
}

emit ConsolidationFailed(sourcePubKeys, sourcePubKeys.length);
}

/**
* @notice Confirms that a consolidation has completed successfully on the beacon chain.
* This reduces the number of active deposited validators managed by this strategy which
* reduces the strategy's balance.
* @param consolidationCount The number of source validators that were consolidated.
*/
function confirmConsolidation(uint256 consolidationCount)
external
nonReentrant
whenNotPaused
onlyRegistrator
{
// Store the reduced number of active deposited validators
// managed by this strategy
activeDepositedValidators -= consolidationCount;

emit ConsolidationConfirmed(
consolidationCount,
activeDepositedValidators
);
}

/***************************************
Abstract
****************************************/
Expand Down
68 changes: 68 additions & 0 deletions contracts/deploy/hoodi/033_validator_consolidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const {
upgradeCompoundingStakingSSVStrategy,
upgradeNativeStakingSSVStrategy,
} = require("../deployActions");
const { deployWithConfirmation } = require("../../utils/deploy");
const { getDefenderSigner, getSigner } = require("../../utils/signers");
const { isFork } = require("../../test/helpers.js");
const { resolveContract } = require("../../utils/resolvers");

const mainExport = async () => {
const sDeployer = isFork ? await getSigner() : await getDefenderSigner();
const deployerAddress = await sDeployer.getAddress();

// 1. Upgrade the Native Staking Strategy
await upgradeNativeStakingSSVStrategy();

const nativeStakingStrategy = await resolveContract(
"NativeStakingSSVStrategyProxy",
"NativeStakingSSVStrategy"
);

// 2. Upgrade the Compounding Staking Strategy
await upgradeCompoundingStakingSSVStrategy();

const compoundingStakingStrategy = await resolveContract(
"CompoundingStakingSSVStrategyProxy",
"CompoundingStakingSSVStrategy"
);

// 3. Deploy the new Consolidation Controller
console.log(`Deploy ConsolidationController`);
const dConsolidationController = await deployWithConfirmation(
"ConsolidationController",
[
deployerAddress, // Owner
deployerAddress, // Validator Registrator
nativeStakingStrategy.address, // Old Native Staking Strategy 2
nativeStakingStrategy.address, // Old Native Staking Strategy 3
compoundingStakingStrategy.address, // New Compounding Staking Strategy
]
);

// 4. Set the Registrator of the Compounding Staking Strategy to the Consolidation Controller
console.log(
`About to set Registrator of CompoundingStakingSSVStrategy to ConsolidationController`
);
await compoundingStakingStrategy
.connect(sDeployer)
.setRegistrator(dConsolidationController.address);

// 5. Set the Registrator of the Native Staking Strategy to the Consolidation Controller
console.log(
`About to set Registrator of NativeStakingSSVStrategy to ConsolidationController`
);
await nativeStakingStrategy
.connect(sDeployer)
.setRegistrator(dConsolidationController.address);

console.log("Running 033 deployment done");
return true;
};

mainExport.id = "033_validator_consolidation";
mainExport.tags = [];
mainExport.dependencies = [];
mainExport.skip = () => false;

module.exports = mainExport;
61 changes: 61 additions & 0 deletions contracts/deploy/hoodi/034_validator_consolidation_upgrade.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const { upgradeNativeStakingSSVStrategy } = require("../deployActions");
const { deployWithConfirmation } = require("../../utils/deploy");
const { getDefenderSigner, getSigner } = require("../../utils/signers");
const { isFork } = require("../../test/helpers.js");
const { resolveContract } = require("../../utils/resolvers");

const mainExport = async () => {
const sDeployer = isFork ? await getSigner() : await getDefenderSigner();
const deployerAddress = await sDeployer.getAddress();

const nativeStakingStrategy = await resolveContract(
"NativeStakingSSVStrategyProxy",
"NativeStakingSSVStrategy"
);
const compoundingStakingStrategy = await resolveContract(
"CompoundingStakingSSVStrategyProxy",
"CompoundingStakingSSVStrategy"
);

// 1. Upgrade the Native Staking Strategy
await upgradeNativeStakingSSVStrategy();

// 2. Deploy the new Consolidation Controller
console.log(`Deploy ConsolidationController`);
const dConsolidationController = await deployWithConfirmation(
"ConsolidationController",
[
deployerAddress, // Owner
deployerAddress, // Validator Registrator
nativeStakingStrategy.address, // Old Native Staking Strategy 2
nativeStakingStrategy.address, // Old Native Staking Strategy 3
compoundingStakingStrategy.address, // New Compounding Staking Strategy
]
);

// 3. Set the Registrator of the Compounding Staking Strategy to the Consolidation Controller
console.log(
`About to set Registrator of CompoundingStakingSSVStrategy to ConsolidationController`
);
await compoundingStakingStrategy
.connect(sDeployer)
.setRegistrator(dConsolidationController.address);

// 4. Set the Registrator of the Native Staking Strategy to the Consolidation Controller
console.log(
`About to set Registrator of NativeStakingSSVStrategy to ConsolidationController`
);
await nativeStakingStrategy
.connect(sDeployer)
.setRegistrator(dConsolidationController.address);

console.log("Running 034 deployment done");
return true;
};

mainExport.id = "034_validator_consolidation_upgrade";
mainExport.tags = [];
mainExport.dependencies = [];
mainExport.skip = () => false;

module.exports = mainExport;
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const addresses = require("../../utils/addresses");
const { deploymentWithGovernanceProposal } = require("../../utils/deploy");

module.exports = deploymentWithGovernanceProposal(
Expand Down
Loading
Loading