Skip to content
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
97 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
962c8d0
Merge remote-tracking branch 'origin/master' into nicka/consolidation
naddison36 Feb 17, 2026
7934dc9
Bumped deploy script number
naddison36 Feb 17, 2026
6e5d924
WIP SSV upgrade changes
naddison36 Feb 17, 2026
6f583de
Revert changes to old native staking tests
naddison36 Feb 17, 2026
b31dcc0
prettier
naddison36 Feb 18, 2026
d8bb3e2
Added migrateClusterToETH to the Compounding Staking Strategy
naddison36 Feb 18, 2026
1f58f61
Add ETH amount to migrateCluster HH task
naddison36 Feb 18, 2026
6743883
Upgraded Native Staking Strategy as its needed for Hoodi testing
naddison36 Feb 18, 2026
37244cf
Fixed migrateCluster Hardhat task
naddison36 Feb 18, 2026
abeb8f7
Merge remote-tracking branch 'origin/master' into nicka/consolidation
naddison36 Feb 19, 2026
460a632
Merge remote-tracking branch 'origin/master' into nicka/consolidation
naddison36 Feb 19, 2026
098dd2c
Added diagrams for CompoundingStakingSSVStrategy and ConsolidationCon…
naddison36 Feb 19, 2026
20f6bbc
Fixes to consolidation Hardhat tasks
naddison36 Feb 19, 2026
5992d37
Added index option to migrateCluster Hardhat task
naddison36 Feb 19, 2026
791cd26
Disable old Native Staking fork tests
naddison36 Feb 19, 2026
6efdf3e
Removed isolated consolidation fork test as its covered in the consol…
naddison36 Feb 19, 2026
476b571
Added coverage of removeSsvValidator on Consolidation Controller
naddison36 Feb 19, 2026
5037cd0
Increased Consolidation Controller code coverage
naddison36 Feb 20, 2026
6b1a404
Merge remote-tracking branch 'origin/master' into nicka/consolidation
naddison36 Feb 26, 2026
d04cd87
Update balance proofs for validator consolidation fork tests
naddison36 Mar 2, 2026
86af851
Updated Consolidation fork tests
naddison36 Mar 3, 2026
63cf152
Improved verifyBalances for test data
naddison36 Mar 3, 2026
43b9215
More fork test fixes
naddison36 Mar 3, 2026
af4ff5b
More fixes to the fork tests
naddison36 Mar 3, 2026
140b3b9
Increased the consolidation fee to cover high usage.
naddison36 Mar 3, 2026
e2989ef
Added consolFee Hardhat task
naddison36 Mar 3, 2026
7504421
OGVC-02 Consolidation Fee Underpayment Silently Drains Consensus Rewa…
naddison36 Mar 5, 2026
75326d7
OGVC-03 Permissionless snapBalances() Can DoS Consolidation Requests …
naddison36 Mar 5, 2026
40a0629
Increased MIN_CONSOLIDATION_PERIOD to 261 epochs (#2821)
naddison36 Mar 5, 2026
4b92b79
Add a zero length check on sourcePubKeys at the beginning of requestC…
naddison36 Mar 5, 2026
10ea3fc
OGVC-01 ConsolidationController Relies On Offchain Trust Assumptions F…
naddison36 Mar 5, 2026
f0d2343
Added a MIN_CONSOLIDATION_PERIOD check to snapBalances() when a conso…
naddison36 Mar 5, 2026
1b37df4
OGVC-08 Miscellaneous General Comments (#2823)
naddison36 Mar 5, 2026
ea3a3e0
OGVC Integration branch: all fixes combined (#2827)
naddison36 Mar 5, 2026
a00291a
Merge remote-tracking branch 'origin/master' into nicka/consolidation
naddison36 Mar 5, 2026
bf638e0
Bump deploy number
naddison36 Mar 5, 2026
7ceb4ac
Fixed consolidation fork tests
naddison36 Mar 5, 2026
8586428
Prettier
naddison36 Mar 5, 2026
b04eb84
Bump the crosschain_upgrade_remote deploy number
naddison36 Mar 5, 2026
40f1261
Merge remote-tracking branch 'origin/master' into nicka/consolidation
naddison36 Mar 9, 2026
70b9566
Bumped deploy number
naddison36 Mar 9, 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 @@ -1013,7 +1018,7 @@ abstract contract CompoundingValidatorManager is Governable, Pausable {
function verifyBalances(
BalanceProofs calldata balanceProofs,
PendingDepositProofs calldata pendingDepositProofs
) external {
) external onlyRegistrator {
Comment thread
sparrowDom marked this conversation as resolved.
// 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.
Comment thread
sparrowDom marked this conversation as resolved.
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;
154 changes: 154 additions & 0 deletions contracts/deploy/mainnet/169_staking_consolidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
const { deploymentWithGovernanceProposal } = require("../../utils/deploy");
const { beaconChainGenesisTimeMainnet } = require("../../utils/constants");
const addresses = require("../../utils/addresses");

module.exports = deploymentWithGovernanceProposal(
{
deployName: "169_staking_consolidation",
forceDeploy: false,
//forceSkip: true,
reduceQueueTime: true,
deployerIsProposer: false,
// proposalId: "",
},
async ({ deployWithConfirmation, ethers }) => {
// Current contracts
const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy");

const cCompoundingStakingStrategyProxy = await ethers.getContract(
"CompoundingStakingSSVStrategyProxy"
);
const cCompoundingStakingSSVStrategy = await ethers.getContractAt(
"CompoundingStakingSSVStrategy",
cCompoundingStakingStrategyProxy.address
);
const cBeaconProofs = await ethers.getContract("BeaconProofs");

const cNativeStakingStrategy2Proxy = await ethers.getContract(
"NativeStakingSSVStrategy2Proxy"
);
const cNativeStakingFeeAccumulator2Proxy = await ethers.getContract(
"NativeStakingFeeAccumulator2Proxy"
);
const cNativeStakingStrategy2 = await ethers.getContractAt(
"NativeStakingSSVStrategy",
cNativeStakingStrategy2Proxy.address
);
const cNativeStakingStrategy3Proxy = await ethers.getContract(
"NativeStakingSSVStrategy3Proxy"
);
const cNativeStakingFeeAccumulator3Proxy = await ethers.getContract(
"NativeStakingFeeAccumulator3Proxy"
);
const cNativeStakingStrategy3 = await ethers.getContractAt(
"NativeStakingSSVStrategy",
cNativeStakingStrategy3Proxy.address
);

// Deployer Actions
// ----------------

// 1. Deploy the new Compounding Staking Strategy contracts

console.log("Deploy CompoundingStakingSSVStrategy");
const dCompoundingStakingStrategy = await deployWithConfirmation(
"CompoundingStakingSSVStrategy",
[
[addresses.zero, cOETHVaultProxy.address], //_baseConfig
addresses.mainnet.WETH, // wethAddress
addresses.mainnet.SSV, // ssvToken
addresses.mainnet.SSVNetwork, // ssvNetwork
addresses.mainnet.beaconChainDepositContract, // depositContractMock
cBeaconProofs.address, // BeaconProofs
beaconChainGenesisTimeMainnet,
]
);

// 2. Deploy the new Native Staking Strategy implementations

console.log(`About to deploy NativeStakingSSVStrategy implementations`);
const dNativeStakingStrategy2Impl = await deployWithConfirmation(
"NativeStakingSSVStrategy",
[
[addresses.zero, cOETHVaultProxy.address], //_baseConfig
addresses.mainnet.WETH, // wethAddress
addresses.mainnet.SSV, // ssvToken
addresses.mainnet.SSVNetwork, // ssvNetwork
500, // maxValidators
cNativeStakingFeeAccumulator2Proxy.address, // feeAccumulator
addresses.mainnet.beaconChainDepositContract, // beacon chain deposit contract
]
);
const dNativeStakingStrategy3Impl = await deployWithConfirmation(
"NativeStakingSSVStrategy",
[
[addresses.zero, cOETHVaultProxy.address], //_baseConfig
addresses.mainnet.WETH, // wethAddress
addresses.mainnet.SSV, // ssvToken
addresses.mainnet.SSVNetwork, // ssvNetwork
500, // maxValidators
cNativeStakingFeeAccumulator3Proxy.address, // feeAccumulator
addresses.mainnet.beaconChainDepositContract, // beacon chain deposit contract
]
);

// 3. Deploy the new Consolidation Controller
console.log(`Deploy ConsolidationController`);
const dConsolidationController = await deployWithConfirmation(
"ConsolidationController",
[
addresses.mainnet.Guardian, // Admin 5/8 multisig
addresses.mainnet.validatorRegistrator, // Defender Relayer
cNativeStakingStrategy2.address, // Old Native Staking Strategy 2
cNativeStakingStrategy3.address, // Old Native Staking Strategy 3
cCompoundingStakingSSVStrategy.address, // New Compounding Staking Strategy
]
);

console.log(`Finished deploying contracts`);

// Governance Actions
// ----------------
return {
name: `Update old Native Staking Strategies and new Compounding Staking Strategy`,
actions: [
// 1. Upgrade Compounding Staking Strategy
{
contract: cCompoundingStakingStrategyProxy,
signature: "upgradeTo(address)",
args: [dCompoundingStakingStrategy.address],
},
// 2. Upgrade Native Staking Strategy 2
{
contract: cNativeStakingStrategy2Proxy,
signature: "upgradeTo(address)",
args: [dNativeStakingStrategy2Impl.address],
},
// 3. Upgrade Native Staking Strategy 3
{
contract: cNativeStakingStrategy3Proxy,
signature: "upgradeTo(address)",
args: [dNativeStakingStrategy3Impl.address],
},
// 4. Set the Registrator of the Compounding Staking Strategy to the Consolidation Controller
{
contract: cCompoundingStakingSSVStrategy,
signature: "setRegistrator(address)",
args: [dConsolidationController.address],
},
// 5. Set the Registrator of the Native Staking Strategy 2 to the Consolidation Controller
{
contract: cNativeStakingStrategy2,
signature: "setRegistrator(address)",
args: [dConsolidationController.address],
},
// 6. Set the Registrator of the Native Staking Strategy 3 to the Consolidation Controller
{
contract: cNativeStakingStrategy3,
signature: "setRegistrator(address)",
args: [dConsolidationController.address],
},
],
};
}
);
Loading
Loading