Skip to content

Commit

Permalink
bulk remove validators
Browse files Browse the repository at this point in the history
  • Loading branch information
mtabasco committed Jan 15, 2024
1 parent 5638b7f commit 6431a00
Show file tree
Hide file tree
Showing 10 changed files with 436 additions and 311 deletions.
8 changes: 8 additions & 0 deletions contracts/SSVNetwork.sol
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ contract SSVNetwork is
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]);
}

function bulkRemoveValidator(
bytes[] calldata publicKeys,
uint64[] calldata operatorIds,
Cluster memory cluster
) external override {
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]);
}

function liquidate(
address clusterOwner,
uint64[] calldata operatorIds,
Expand Down
14 changes: 12 additions & 2 deletions contracts/interfaces/ISSVClusters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ interface ISSVClusters is ISSVNetworkCore {
) external;

/// @notice Registers new validators on the SSV Network
/// @param publicKeys The public key of the new validator
/// @param publicKeys The public keys of the new validators
/// @param operatorIds Array of IDs of operators managing this validator
/// @param sharesData Encrypted shares related to the new validator
/// @param sharesData Encrypted shares related to the new validators
/// @param amount Amount of SSV tokens to be deposited
/// @param cluster Cluster to be used with the new validator
function bulkRegisterValidator(
Expand All @@ -38,6 +38,16 @@ interface ISSVClusters is ISSVNetworkCore {
/// @param cluster Cluster associated with the validator
function removeValidator(bytes calldata publicKey, uint64[] memory operatorIds, Cluster memory cluster) external;

/// @notice Removes an existing validator from the SSV Network
/// @param publicKeys The public keys of the validators to be removed
/// @param operatorIds Array of IDs of operators managing the validator
/// @param cluster Cluster associated with the validator
function bulkRemoveValidator(
bytes[] calldata publicKeys,
uint64[] memory operatorIds,
Cluster memory cluster
) external;

/**************************/
/* Cluster External Functions */
/**************************/
Expand Down
24 changes: 20 additions & 4 deletions contracts/libraries/ValidatorLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,27 @@ library ValidatorLib {
s.validatorPKs[hashedPk] = bytes32(uint256(keccak256(abi.encodePacked(operatorIds))) | uint256(0x01)); // set LSB to 1
}

function hashOperatorIds(uint64[] calldata operatorIds) internal pure returns (bytes32) {
bytes32 mask = ~bytes32(uint256(1)); // All bits set to 1 except LSB
return keccak256(abi.encodePacked(operatorIds)) & mask; // Clear LSB of provided operator ids
}

function validateStates(
bytes[] calldata publicKeys,
uint64[] calldata operatorIds,
StorageData storage s
) internal view returns (bytes32[] memory hashedValidator) {
hashedValidator = new bytes32[](publicKeys.length);
bytes32 hashedOperatorIds = hashOperatorIds(operatorIds);

for (uint i; i < publicKeys.length; ++i) {
hashedValidator[i] = validateState(publicKeys[i], hashedOperatorIds, s);
}
}

function validateState(
bytes calldata publicKey,
uint64[] calldata operatorIds,
bytes32 hashedOperatorIds,
StorageData storage s
) internal view returns (bytes32 hashedValidator) {
hashedValidator = keccak256(abi.encodePacked(publicKey, msg.sender));
Expand All @@ -57,10 +75,8 @@ library ValidatorLib {
if (validatorData == bytes32(0)) {
revert ISSVNetworkCore.ValidatorDoesNotExist();
}
bytes32 mask = ~bytes32(uint256(1)); // All bits set to 1 except LSB

bytes32 hashedOperatorIds = keccak256(abi.encodePacked(operatorIds)) & mask; // Clear LSB of provided operator ids
if ((validatorData & mask) != hashedOperatorIds) {
if ((validatorData & ~bytes32(uint256(1))) != hashedOperatorIds) { // All bits set to 1 except LSB
// Clear LSB of stored validator data and compare
revert ISSVNetworkCore.IncorrectValidatorState();
}
Expand Down
60 changes: 51 additions & 9 deletions contracts/modules/SSVClusters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,18 @@ contract SSVClusters is ISSVClusters {
) external override {
StorageData storage s = SSVStorage.load();

bytes32 hashedValidator = ValidatorLib.validateState(publicKey, operatorIds, s);
bytes32 hashedOperatorIds = ValidatorLib.hashOperatorIds(operatorIds);
bytes32 hashedValidator = ValidatorLib.validateState(publicKey, hashedOperatorIds, s);

bytes32 hashedCluster = cluster.validateHashedCluster(msg.sender, operatorIds, s);

{
if (cluster.active) {
StorageProtocol storage sp = SSVStorageProtocol.load();
(uint64 clusterIndex, ) = OperatorLib.updateClusterOperators(operatorIds, false, false, 1, s, sp);
if (cluster.active) {
StorageProtocol storage sp = SSVStorageProtocol.load();
(uint64 clusterIndex, ) = OperatorLib.updateClusterOperators(operatorIds, false, false, 1, s, sp);

cluster.updateClusterData(clusterIndex, sp.currentNetworkFeeIndex());
cluster.updateClusterData(clusterIndex, sp.currentNetworkFeeIndex());

sp.updateDAO(false, 1);
}
sp.updateDAO(false, 1);
}

--cluster.validatorCount;
Expand All @@ -112,6 +111,48 @@ contract SSVClusters is ISSVClusters {
emit ValidatorRemoved(msg.sender, operatorIds, publicKey, cluster);
}

function bulkRemoveValidator(
bytes[] calldata publicKeys,
uint64[] calldata operatorIds,
Cluster memory cluster
) external override {
StorageData storage s = SSVStorage.load();

bytes32 hashedCluster = cluster.validateHashedCluster(msg.sender, operatorIds, s);

bytes32[] memory hashedValidators = ValidatorLib.validateStates(publicKeys, operatorIds, s);

uint32 validatorsLength = uint32(publicKeys.length);

if (cluster.active) {
StorageProtocol storage sp = SSVStorageProtocol.load();
(uint64 clusterIndex, ) = OperatorLib.updateClusterOperators(
operatorIds,
false,
false,
validatorsLength,
s,
sp
);

cluster.updateClusterData(clusterIndex, sp.currentNetworkFeeIndex());

sp.updateDAO(false, validatorsLength);
}

for (uint i; i < validatorsLength; ++i) {
delete s.validatorPKs[hashedValidators[i]];
}

cluster.validatorCount -= validatorsLength;

s.clusters[hashedCluster] = cluster.hashClusterData();

for (uint i; i < validatorsLength; ++i) {
emit ValidatorRemoved(msg.sender, operatorIds, publicKeys[i], cluster);
}
}

function liquidate(address clusterOwner, uint64[] calldata operatorIds, Cluster memory cluster) external override {
StorageData storage s = SSVStorage.load();

Expand Down Expand Up @@ -280,7 +321,8 @@ contract SSVClusters is ISSVClusters {
}

function exitValidator(bytes calldata publicKey, uint64[] calldata operatorIds) external override {
ValidatorLib.validateState(publicKey, operatorIds, SSVStorage.load());
bytes32 hashedOperatorIds = ValidatorLib.hashOperatorIds(operatorIds);
ValidatorLib.validateState(publicKey, hashedOperatorIds, SSVStorage.load());

emit ValidatorExited(msg.sender, operatorIds, publicKey);
}
Expand Down
16 changes: 16 additions & 0 deletions contracts/test/SSVNetworkUpgrade.sol
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,22 @@ contract SSVNetworkUpgrade is
);
}

function bulkRemoveValidator(
bytes[] calldata publicKeys,
uint64[] calldata operatorIds,
ISSVNetworkCore.Cluster memory cluster
) external override {
_delegateCall(
SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS],
abi.encodeWithSignature(
"bulkRemoveValidator(bytes[],uint64[],(uint32,uint64,uint64,bool,uint256))",
publicKeys,
operatorIds,
cluster
)
);
}

function liquidate(address owner, uint64[] calldata operatorIds, ISSVNetworkCore.Cluster memory cluster) external {
_delegateCall(
SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS],
Expand Down
44 changes: 43 additions & 1 deletion test/helpers/contract-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ export let DB: any;
export let CONFIG: any;
export let SSV_MODULES: any;

export const DEFAULT_OPERATOR_IDS = {
4: [1, 2, 3, 4],
7: [1, 2, 3, 4, 5, 6, 7],
10: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
13: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
};

const getSecretSharedPayload = async (validator: Validator, operatorIds: number[], ownerId: number) => {
const selOperators = DB.operators.filter((item: Operator) => operatorIds.includes(item.id));
const operators = selOperators.map((item: Operator) => ({ id: item.id, operatorKey: item.operatorKey }));
Expand Down Expand Up @@ -128,6 +135,13 @@ export const initializeContract = async () => {
ssvDAOMod: {},
ssvViewsMod: {},
ownerNonce: 0,
initialClusterState: {
validatorCount: 0,
networkFeeIndex: 0,
index: 0,
balance: 0,
active: true
}
};

SSV_MODULES = {
Expand Down Expand Up @@ -304,12 +318,40 @@ export const registerValidators = async (
return { regValidators, args };
};

export const bulkRegisterValidators = async (
ownerId: number,
numberOfValidators: number,
operatorIds: number[],
minDepositAmount: any,
cluster: any,
gasGroups?: GasGroup[]
) => {
const pks = Array.from({ length: numberOfValidators }, (_, index) => DataGenerator.publicKey(index + 1));
const shares = Array.from({ length: numberOfValidators }, (_, index) => DataGenerator.shares(1, index, operatorIds.length));
const depositAmount = minDepositAmount * numberOfValidators;

await DB.ssvToken.connect(DB.owners[ownerId]).approve(DB.ssvNetwork.contract.address, depositAmount);

const result = await trackGas(DB.ssvNetwork.contract.connect(DB.owners[ownerId]).bulkRegisterValidator(
pks,
operatorIds,
shares,
depositAmount,
cluster
), gasGroups);

return {
args: result.eventsByName.ValidatorAdded[0].args,
pks
};
};

export const coldRegisterValidator = async () => {
const ssvKeys = new SSVKeys();
const keyShares = new KeyShares();

const validator = DB.validators[0];
const operators = DB.operators.map((item: Operator) => ({ id: item.id, operatorKey: item.operatorKey }));
const operators = DB.operators.slice(0, 4).map((item: Operator) => ({ id: item.id, operatorKey: item.operatorKey }));
const publicKey = validator.publicKey;
const privateKey = validator.privateKey;
const threshold = await ssvKeys.createThreshold(privateKey, operators);
Expand Down
14 changes: 13 additions & 1 deletion test/helpers/gas-usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,13 @@ export enum GasGroup {
BULK_REGISTER_10_VALIDATOR_EXISTING_CLUSTER_13,

REMOVE_VALIDATOR,
BULK_REMOVE_10_VALIDATOR_4,
REMOVE_VALIDATOR_7,
BULK_REMOVE_10_VALIDATOR_7,
REMOVE_VALIDATOR_10,
BULK_REMOVE_10_VALIDATOR_10,
REMOVE_VALIDATOR_13,
BULK_REMOVE_10_VALIDATOR_13,
DEPOSIT,
WITHDRAW_CLUSTER_BALANCE,
WITHDRAW_OPERATOR_BALANCE,
Expand Down Expand Up @@ -102,13 +106,21 @@ const MAX_GAS_PER_GROUP: any = {
[GasGroup.REGISTER_VALIDATOR_NEW_STATE_13]: 430500,
[GasGroup.REGISTER_VALIDATOR_WITHOUT_DEPOSIT_13]: 393200,

[GasGroup.BULK_REGISTER_10_VALIDATOR_NEW_STATE_13]: 1649500,
[GasGroup.BULK_REGISTER_10_VALIDATOR_NEW_STATE_13]: 1649600,
[GasGroup.BULK_REGISTER_10_VALIDATOR_EXISTING_CLUSTER_13]: 1633000,

[GasGroup.REMOVE_VALIDATOR]: 115000,
[GasGroup.BULK_REMOVE_10_VALIDATOR_4]: 196000,

[GasGroup.REMOVE_VALIDATOR_7]: 156000,
[GasGroup.BULK_REMOVE_10_VALIDATOR_7]: 248500,

[GasGroup.REMOVE_VALIDATOR_10]: 197500,
[GasGroup.BULK_REMOVE_10_VALIDATOR_10]: 301000,

[GasGroup.REMOVE_VALIDATOR_13]: 239000,
[GasGroup.BULK_REMOVE_10_VALIDATOR_13]: 353200,

[GasGroup.DEPOSIT]: 77500,
[GasGroup.WITHDRAW_CLUSTER_BALANCE]: 94500,
[GasGroup.WITHDRAW_OPERATOR_BALANCE]: 64900,
Expand Down
Loading

0 comments on commit 6431a00

Please sign in to comment.