Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bulk exit and remove validator #2185

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,9 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
/// The `ValidatorStakeData` struct contains the pubkey, signature and depositDataRoot.
/// Only the registrator can call this function.
// slither-disable-start reentrancy-eth
function stakeEth(ValidatorStakeData[] calldata validators)
external
onlyRegistrator
whenNotPaused
nonReentrant
{
function stakeEth(
ValidatorStakeData[] calldata validators
) external onlyRegistrator whenNotPaused nonReentrant {
uint256 requiredETH = validators.length * FULL_STAKE;

// Check there is enough WETH from the deposits sitting in this strategy contract
Expand Down Expand Up @@ -267,66 +264,87 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
);
}

// slither-disable-end reentrancy-no-eth

/// @notice Exit a validator from the Beacon chain.
/// @notice Exit validators from the Beacon chain.
/// The staked ETH will eventually swept to this native staking strategy.
/// Only the registrator can call this function.
/// @param publicKey The public key of the validator
/// @param publicKeys List of SSV validator public keys
/// @param operatorIds The operator IDs of the SSV Cluster
// slither-disable-start reentrancy-no-eth
function exitSsvValidator(
bytes calldata publicKey,
function exitSsvValidators(
bytes[] calldata publicKeys,
uint64[] calldata operatorIds
) external onlyRegistrator whenNotPaused {
bytes32 pubKeyHash = keccak256(publicKey);
VALIDATOR_STATE currentState = validatorsStates[pubKeyHash];
require(currentState == VALIDATOR_STATE.STAKED, "Validator not staked");
) external onlyRegistrator whenNotPaused nonReentrant {
ISSVNetwork(SSV_NETWORK).bulkExitValidator(publicKeys, operatorIds);

ISSVNetwork(SSV_NETWORK).exitValidator(publicKey, operatorIds);
bytes32 pubKeyHash;
VALIDATOR_STATE currentState;
for (uint256 i = 0; i < publicKeys.length; ++i) {
pubKeyHash = keccak256(publicKeys[i]);
currentState = validatorsStates[pubKeyHash];

validatorsStates[pubKeyHash] = VALIDATOR_STATE.EXITING;
// Check each validator has not already been staked.
// This would normally be done before the external call but is after
// so only one for loop of the validators is needed.
require(
currentState == VALIDATOR_STATE.STAKED,
"Validator not staked"
);

emit SSVValidatorExitInitiated(pubKeyHash, publicKey, operatorIds);
}
// Store the new validator state
validatorsStates[pubKeyHash] = VALIDATOR_STATE.EXITING;

// slither-disable-end reentrancy-no-eth
emit SSVValidatorExitInitiated(
pubKeyHash,
publicKeys[i],
operatorIds
);
}
}

/// @notice Remove a validator from the SSV Cluster.
/// @notice Remove validators from the SSV Cluster.
/// Make sure `exitSsvValidator` is called before and the validate has exited the Beacon chain.
/// If removed before the validator has exited the beacon chain will result in the validator being slashed.
/// Only the registrator can call this function.
/// @param publicKey The public key of the validator
/// @param publicKeys List of SSV validator public keys
/// @param operatorIds The operator IDs of the SSV Cluster
/// @param cluster The SSV cluster details including the validator count and SSV balance
// slither-disable-start reentrancy-no-eth
function removeSsvValidator(
bytes calldata publicKey,
function removeSsvValidators(
bytes[] calldata publicKeys,
uint64[] calldata operatorIds,
Cluster calldata cluster
) external onlyRegistrator whenNotPaused {
bytes32 pubKeyHash = keccak256(publicKey);
VALIDATOR_STATE currentState = validatorsStates[pubKeyHash];
// Can remove SSV validators that were incorrectly registered and can not be deposited to.
require(
currentState == VALIDATOR_STATE.EXITING ||
currentState == VALIDATOR_STATE.REGISTERED,
"Validator not regd or exiting"
);

ISSVNetwork(SSV_NETWORK).removeValidator(
publicKey,
) external onlyRegistrator whenNotPaused nonReentrant {
ISSVNetwork(SSV_NETWORK).bulkRemoveValidator(
publicKeys,
operatorIds,
cluster
);

validatorsStates[pubKeyHash] = VALIDATOR_STATE.EXIT_COMPLETE;
bytes32 pubKeyHash;
VALIDATOR_STATE currentState;
for (uint256 i = 0; i < publicKeys.length; ++i) {
pubKeyHash = keccak256(publicKeys[i]);
currentState = validatorsStates[pubKeyHash];

// Check each validator is either registered or exited.
// This would normally be done before the external call but is after
// so only one for loop of the validators is needed.
require(
currentState == VALIDATOR_STATE.EXITING ||
currentState == VALIDATOR_STATE.REGISTERED,
"Validator not regd or exiting"
);

// Store the new validator state
validatorsStates[pubKeyHash] = VALIDATOR_STATE.EXIT_COMPLETE;

emit SSVValidatorExitCompleted(pubKeyHash, publicKey, operatorIds);
emit SSVValidatorExitCompleted(
pubKeyHash,
publicKeys[i],
operatorIds
);
}
}

// slither-disable-end reentrancy-no-eth

/// @notice Deposits more SSV Tokens to the SSV Network contract which is used to pay the SSV Operators.
/// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds.
/// uses "onlyStrategist" modifier so continuous front-running can't DOS our maintenance service
Expand Down
17 changes: 17 additions & 0 deletions contracts/deploy/holesky/019_upgrade_strategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const { upgradeNativeStakingSSVStrategy } = require("../deployActions");

const mainExport = async () => {
console.log("Running 019 deployment on Holesky...");

await upgradeNativeStakingSSVStrategy();

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

mainExport.id = "019_upgrade_strategy";
mainExport.tags = [];
mainExport.dependencies = [];
mainExport.skip = () => false;

module.exports = mainExport;
68 changes: 34 additions & 34 deletions contracts/docs/NativeStakingSSVStrategyHierarchy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions contracts/docs/NativeStakingSSVStrategySquashed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 14 additions & 8 deletions contracts/tasks/ssv.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
const { parseUnits, formatUnits, solidityPack } = require("ethers/lib/utils");
const {
parseUnits,
formatUnits,
solidityPack,
hexlify,
} = require("ethers/lib/utils");

const addresses = require("../utils/addresses");
const { resolveContract } = require("../utils/resolvers");
Expand All @@ -7,11 +12,10 @@ const { getClusterInfo } = require("../utils/ssv");
const { networkMap } = require("../utils/hardhat-helpers");
const { logTxDetails } = require("../utils/txLogger");
const { resolveNativeStakingStrategyProxy } = require("./validator");
const { checkPubkeyFormat } = require("./taskUtils");

const log = require("../utils/logger")("task:ssv");

async function removeValidator({ index, pubkey, operatorids }) {
async function removeValidators({ index, pubkeys, operatorids }) {
const signer = await getSigner();

log(`Splitting operator IDs ${operatorids}`);
Expand All @@ -29,12 +33,14 @@ async function removeValidator({ index, pubkey, operatorids }) {
ownerAddress: strategy.address,
});

log(`About to remove validator`);
pubkey = checkPubkeyFormat(pubkey);
log(`Splitting public keys ${pubkeys}`);
const pubKeys = pubkeys.split(",").map((pubkey) => hexlify(pubkey));

log(`About to remove validators: ${pubKeys}`);
const tx = await strategy
.connect(signer)
.removeSsvValidator(pubkey, operatorIds, cluster);
await logTxDetails(tx, "removeSsvValidator");
.removeSsvValidators(pubKeys, operatorIds, cluster);
await logTxDetails(tx, "removeSsvValidators");
}

const printClusterInfo = async (options) => {
Expand Down Expand Up @@ -121,5 +127,5 @@ module.exports = {
printClusterInfo,
depositSSV,
calcDepositRoot,
removeValidator,
removeValidators,
};
14 changes: 0 additions & 14 deletions contracts/tasks/taskUtils.js

This file was deleted.

Loading
Loading