diff --git a/contracts/libraries/ClusterLib.sol b/contracts/libraries/ClusterLib.sol index 178701f7..b839501d 100644 --- a/contracts/libraries/ClusterLib.sol +++ b/contracts/libraries/ClusterLib.sol @@ -3,10 +3,14 @@ pragma solidity 0.8.18; import "../interfaces/ISSVNetworkCore.sol"; import "./SSVStorage.sol"; +import "./SSVStorageProtocol.sol"; +import "./OperatorLib.sol"; +import "./ProtocolLib.sol"; import "./Types.sol"; library ClusterLib { using Types64 for uint64; + using ProtocolLib for StorageProtocol; function updateBalance( ISSVNetworkCore.Cluster memory cluster, @@ -80,4 +84,71 @@ library ClusterLib { ) ); } + + function validateClusterOnRegistration( + ISSVNetworkCore.Cluster memory cluster, + uint64[] memory operatorIds, + StorageData storage s + ) internal view returns (bytes32 hashedCluster) { + hashedCluster = keccak256(abi.encodePacked(msg.sender, operatorIds)); + + bytes32 clusterData = s.clusters[hashedCluster]; + if (clusterData == bytes32(0)) { + if ( + cluster.validatorCount != 0 || + cluster.networkFeeIndex != 0 || + cluster.index != 0 || + cluster.balance != 0 || + !cluster.active + ) { + revert ISSVNetworkCore.IncorrectClusterState(); + } + } else if (clusterData != hashClusterData(cluster)) { + revert ISSVNetworkCore.IncorrectClusterState(); + } else { + validateClusterIsNotLiquidated(cluster); + } + } + + function updateClusterOnRegistration( + ISSVNetworkCore.Cluster memory cluster, + uint64[] memory operatorIds, + bytes32 hashedCluster, + uint32 validatorCountDelta, + StorageData storage s, + StorageProtocol storage sp + ) internal { + uint64 burnRate; + + if (cluster.active) { + (uint64 clusterIndex, uint64 burnRate) = OperatorLib.updateClusterOperators( + operatorIds, + true, + true, + validatorCountDelta, + s, + sp + ); + + updateClusterData(cluster, clusterIndex, sp.currentNetworkFeeIndex()); + + sp.updateDAO(true, validatorCountDelta); + } + + cluster.validatorCount += validatorCountDelta; + + if ( + isLiquidatable( + cluster, + burnRate, + sp.networkFee, + sp.minimumBlocksBeforeLiquidation, + sp.minimumLiquidationCollateral + ) + ) { + revert ISSVNetworkCore.InsufficientBalance(); + } + + s.clusters[hashedCluster] = hashClusterData(cluster); + } } diff --git a/contracts/libraries/OperatorLib.sol b/contracts/libraries/OperatorLib.sol index f0672d59..cf2adcd4 100644 --- a/contracts/libraries/OperatorLib.sol +++ b/contracts/libraries/OperatorLib.sol @@ -30,29 +30,64 @@ library OperatorLib { if (operator.owner != msg.sender) revert ISSVNetworkCore.CallerNotOwner(); } - function updateOperators( + function updateClusterOperators( uint64[] memory operatorIds, + bool isRegisteringValidator, bool increaseValidatorCount, uint32 deltaValidatorCount, StorageData storage s, StorageProtocol storage sp - ) internal returns (uint64 clusterIndex, uint64 burnRate) { + ) internal returns (uint64 cumulativeIndex, uint64 cumulativeFee) { uint256 operatorsLength = operatorIds.length; for (uint256 i; i < operatorsLength; ) { uint64 operatorId = operatorIds[i]; - ISSVNetworkCore.Operator storage operator = s.operators[operatorId]; - if (operator.snapshot.block != 0) { - updateSnapshotSt(operator); - if (!increaseValidatorCount) { - operator.validatorCount -= deltaValidatorCount; - } else if ((operator.validatorCount += deltaValidatorCount) > sp.validatorsPerOperatorLimit) { + + if (!isRegisteringValidator) { + ISSVNetworkCore.Operator storage operator = s.operators[operatorId]; + + if (operator.snapshot.block != 0) { + updateSnapshotSt(operator); + if (!increaseValidatorCount) { + operator.validatorCount -= deltaValidatorCount; + } else if ((operator.validatorCount += deltaValidatorCount) > sp.validatorsPerOperatorLimit) { + revert ISSVNetworkCore.ExceedValidatorLimit(); + } + + cumulativeFee += operator.fee; + } + cumulativeIndex += operator.snapshot.index; + } else { + if (i + 1 < operatorsLength) { + if (operatorId > operatorIds[i + 1]) { + revert ISSVNetworkCore.UnsortedOperatorsList(); + } else if (operatorId == operatorIds[i + 1]) { + revert ISSVNetworkCore.OperatorsListNotUnique(); + } + } + ISSVNetworkCore.Operator memory operator = s.operators[operatorId]; + + if (operator.snapshot.block == 0) { + revert ISSVNetworkCore.OperatorDoesNotExist(); + } + if (operator.whitelisted) { + address whitelisted = s.operatorsWhitelist[operatorId]; + if (whitelisted != address(0) && whitelisted != msg.sender) { + revert ISSVNetworkCore.CallerNotWhitelisted(); + } + } + + updateSnapshot(operator); + if ((operator.validatorCount += deltaValidatorCount) > sp.validatorsPerOperatorLimit) { revert ISSVNetworkCore.ExceedValidatorLimit(); } - burnRate += operator.fee; + + cumulativeFee += operator.fee; + cumulativeIndex += operator.snapshot.index; + + s.operators[operatorId] = operator; } - clusterIndex += operator.snapshot.index; unchecked { ++i; } diff --git a/contracts/libraries/ValidatorLib.sol b/contracts/libraries/ValidatorLib.sol index 08711c92..2f26e32f 100644 --- a/contracts/libraries/ValidatorLib.sol +++ b/contracts/libraries/ValidatorLib.sol @@ -5,8 +5,23 @@ import "../interfaces/ISSVNetworkCore.sol"; import "./SSVStorage.sol"; library ValidatorLib { + uint64 private constant MIN_OPERATORS_LENGTH = 4; + uint64 private constant MAX_OPERATORS_LENGTH = 13; + uint64 private constant MODULO_OPERATORS_LENGTH = 3; uint64 private constant PUBLIC_KEY_LENGTH = 48; + function validateOperatorsLength(uint64[] memory operatorIds) internal pure { + uint256 operatorsLength = operatorIds.length; + + if ( + operatorsLength < MIN_OPERATORS_LENGTH || + operatorsLength > MAX_OPERATORS_LENGTH || + operatorsLength % MODULO_OPERATORS_LENGTH != 1 + ) { + revert ISSVNetworkCore.InvalidOperatorIdsLength(); + } + } + function registerPublicKeys( bytes[] calldata publicKeys, uint64[] memory operatorIds, diff --git a/contracts/modules/SSVClusters.sol b/contracts/modules/SSVClusters.sol index 16d2f1f0..0a40c5dd 100644 --- a/contracts/modules/SSVClusters.sol +++ b/contracts/modules/SSVClusters.sol @@ -14,9 +14,6 @@ contract SSVClusters is ISSVClusters { using ClusterLib for Cluster; using OperatorLib for Operator; using ProtocolLib for StorageProtocol; - uint64 private constant MIN_OPERATORS_LENGTH = 4; - uint64 private constant MAX_OPERATORS_LENGTH = 13; - uint64 private constant MODULO_OPERATORS_LENGTH = 3; function registerValidator( bytes calldata publicKey, @@ -28,100 +25,15 @@ contract SSVClusters is ISSVClusters { StorageData storage s = SSVStorage.load(); StorageProtocol storage sp = SSVStorageProtocol.load(); - uint256 operatorsLength = operatorIds.length; - { - if ( - operatorsLength < MIN_OPERATORS_LENGTH || - operatorsLength > MAX_OPERATORS_LENGTH || - operatorsLength % MODULO_OPERATORS_LENGTH != 1 - ) { - revert InvalidOperatorIdsLength(); - } + ValidatorLib.validateOperatorsLength(operatorIds); - ValidatorLib.resgisterPublicKey(publicKey, operatorIds, s); - } - bytes32 hashedCluster = keccak256(abi.encodePacked(msg.sender, operatorIds)); + ValidatorLib.resgisterPublicKey(publicKey, operatorIds, s); - { - bytes32 clusterData = s.clusters[hashedCluster]; - if (clusterData == bytes32(0)) { - if ( - cluster.validatorCount != 0 || - cluster.networkFeeIndex != 0 || - cluster.index != 0 || - cluster.balance != 0 || - !cluster.active - ) { - revert IncorrectClusterState(); - } - } else if (clusterData != cluster.hashClusterData()) { - revert IncorrectClusterState(); - } else { - cluster.validateClusterIsNotLiquidated(); - } - } + bytes32 hashedCluster = cluster.validateClusterOnRegistration(operatorIds, s); cluster.balance += amount; - uint64 burnRate; - - if (cluster.active) { - uint64 clusterIndex; - - for (uint256 i; i < operatorsLength; ) { - uint64 operatorId = operatorIds[i]; - { - if (i + 1 < operatorsLength) { - if (operatorId > operatorIds[i + 1]) { - revert UnsortedOperatorsList(); - } else if (operatorId == operatorIds[i + 1]) { - revert OperatorsListNotUnique(); - } - } - } - - Operator memory operator = s.operators[operatorId]; - if (operator.snapshot.block == 0) { - revert OperatorDoesNotExist(); - } - if (operator.whitelisted) { - address whitelisted = s.operatorsWhitelist[operatorId]; - if (whitelisted != address(0) && whitelisted != msg.sender) { - revert CallerNotWhitelisted(); - } - } - operator.updateSnapshot(); - if (++operator.validatorCount > sp.validatorsPerOperatorLimit) { - revert ExceedValidatorLimit(); - } - clusterIndex += operator.snapshot.index; - burnRate += operator.fee; - - s.operators[operatorId] = operator; - - unchecked { - ++i; - } - } - cluster.updateClusterData(clusterIndex, sp.currentNetworkFeeIndex()); - - sp.updateDAO(true, 1); - } - - ++cluster.validatorCount; - - if ( - cluster.isLiquidatable( - burnRate, - sp.networkFee, - sp.minimumBlocksBeforeLiquidation, - sp.minimumLiquidationCollateral - ) - ) { - revert InsufficientBalance(); - } - - s.clusters[hashedCluster] = cluster.hashClusterData(); + cluster.updateClusterOnRegistration(operatorIds, hashedCluster, 1, s, sp); if (amount != 0) { CoreLib.deposit(amount); @@ -142,103 +54,17 @@ contract SSVClusters is ISSVClusters { StorageData storage s = SSVStorage.load(); StorageProtocol storage sp = SSVStorageProtocol.load(); - uint256 operatorsLength = operatorIds.length; uint32 validatorsLength = uint32(publicKeys.length); - { - if ( - operatorsLength < MIN_OPERATORS_LENGTH || - operatorsLength > MAX_OPERATORS_LENGTH || - operatorsLength % MODULO_OPERATORS_LENGTH != 1 - ) { - revert InvalidOperatorIdsLength(); - } + ValidatorLib.validateOperatorsLength(operatorIds); - ValidatorLib.registerPublicKeys(publicKeys, operatorIds, s); - } - bytes32 hashedCluster = keccak256(abi.encodePacked(msg.sender, operatorIds)); + ValidatorLib.registerPublicKeys(publicKeys, operatorIds, s); - { - bytes32 clusterData = s.clusters[hashedCluster]; - if (clusterData == bytes32(0)) { - if ( - cluster.validatorCount != 0 || - cluster.networkFeeIndex != 0 || - cluster.index != 0 || - cluster.balance != 0 || - !cluster.active - ) { - revert IncorrectClusterState(); - } - } else if (clusterData != cluster.hashClusterData()) { - revert IncorrectClusterState(); - } else { - cluster.validateClusterIsNotLiquidated(); - } - } + bytes32 hashedCluster = cluster.validateClusterOnRegistration(operatorIds, s); cluster.balance += amount; - uint64 burnRate; - - if (cluster.active) { - uint64 clusterIndex; - - for (uint256 i; i < operatorsLength; ) { - uint64 operatorId = operatorIds[i]; - { - if (i + 1 < operatorsLength) { - if (operatorId > operatorIds[i + 1]) { - revert UnsortedOperatorsList(); - } else if (operatorId == operatorIds[i + 1]) { - revert OperatorsListNotUnique(); - } - } - } - - Operator memory operator = s.operators[operatorId]; - if (operator.snapshot.block == 0) { - revert OperatorDoesNotExist(); - } - if (operator.whitelisted) { - address whitelisted = s.operatorsWhitelist[operatorId]; - if (whitelisted != address(0) && whitelisted != msg.sender) { - revert CallerNotWhitelisted(); - } - } - operator.updateSnapshot(); - operator.validatorCount += validatorsLength; - if (operator.validatorCount > sp.validatorsPerOperatorLimit) { - revert ExceedValidatorLimit(); - } - clusterIndex += operator.snapshot.index; - burnRate += operator.fee; - - s.operators[operatorId] = operator; - - unchecked { - ++i; - } - } - cluster.updateClusterData(clusterIndex, sp.currentNetworkFeeIndex()); - - sp.updateDAO(true, validatorsLength); - } - - cluster.validatorCount += validatorsLength; - - if ( - cluster.isLiquidatable( - burnRate, - sp.networkFee, - sp.minimumBlocksBeforeLiquidation, - sp.minimumLiquidationCollateral - ) - ) { - revert InsufficientBalance(); - } - - s.clusters[hashedCluster] = cluster.hashClusterData(); + cluster.updateClusterOnRegistration(operatorIds, hashedCluster, validatorsLength, s, sp); if (amount != 0) { CoreLib.deposit(amount); @@ -269,7 +95,7 @@ contract SSVClusters is ISSVClusters { { if (cluster.active) { StorageProtocol storage sp = SSVStorageProtocol.load(); - (uint64 clusterIndex, ) = OperatorLib.updateOperators(operatorIds, false, 1, s, sp); + (uint64 clusterIndex, ) = OperatorLib.updateClusterOperators(operatorIds, false, false, 1, s, sp); cluster.updateClusterData(clusterIndex, sp.currentNetworkFeeIndex()); @@ -294,9 +120,10 @@ contract SSVClusters is ISSVClusters { StorageProtocol storage sp = SSVStorageProtocol.load(); - (uint64 clusterIndex, uint64 burnRate) = OperatorLib.updateOperators( + (uint64 clusterIndex, uint64 burnRate) = OperatorLib.updateClusterOperators( operatorIds, false, + false, cluster.validatorCount, s, sp @@ -345,8 +172,9 @@ contract SSVClusters is ISSVClusters { StorageProtocol storage sp = SSVStorageProtocol.load(); - (uint64 clusterIndex, uint64 burnRate) = OperatorLib.updateOperators( + (uint64 clusterIndex, uint64 burnRate) = OperatorLib.updateClusterOperators( operatorIds, + false, true, cluster.validatorCount, s, diff --git a/test/helpers/gas-usage.ts b/test/helpers/gas-usage.ts index 8748530f..a066383f 100644 --- a/test/helpers/gas-usage.ts +++ b/test/helpers/gas-usage.ts @@ -47,7 +47,7 @@ export enum GasGroup { WITHDRAW_CLUSTER_BALANCE, WITHDRAW_OPERATOR_BALANCE, VALIDATOR_EXIT, - + LIQUIDATE_CLUSTER_4, LIQUIDATE_CLUSTER_7, LIQUIDATE_CLUSTER_10, @@ -71,39 +71,39 @@ const MAX_GAS_PER_GROUP: any = { [GasGroup.REMOVE_OPERATOR]: 70200, [GasGroup.REMOVE_OPERATOR_WITH_WITHDRAW]: 70200, [GasGroup.SET_OPERATOR_WHITELIST]: 84300, - + [GasGroup.DECLARE_OPERATOR_FEE]: 70000, [GasGroup.CANCEL_OPERATOR_FEE]: 41900, [GasGroup.EXECUTE_OPERATOR_FEE]: 52000, [GasGroup.REDUCE_OPERATOR_FEE]: 51900, - [GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER]: 202000, - [GasGroup.REGISTER_VALIDATOR_NEW_STATE]: 221400, - [GasGroup.REGISTER_VALIDATOR_WITHOUT_DEPOSIT]: 181600, + [GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER]: 200500, + [GasGroup.REGISTER_VALIDATOR_NEW_STATE]: 220000, + [GasGroup.REGISTER_VALIDATOR_WITHOUT_DEPOSIT]: 180000, [GasGroup.BULK_REGISTER_10_VALIDATOR_NEW_STATE_4]: 889900, - [GasGroup.BULK_REGISTER_10_VALIDATOR_EXISTING_CLUSTER_4]: 814900, + [GasGroup.BULK_REGISTER_10_VALIDATOR_EXISTING_CLUSTER_4]: 813000, - [GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER_7]: 272900, - [GasGroup.REGISTER_VALIDATOR_NEW_STATE_7]: 289634, - [GasGroup.REGISTER_VALIDATOR_WITHOUT_DEPOSIT_7]: 252500, + [GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER_7]: 271500, + [GasGroup.REGISTER_VALIDATOR_NEW_STATE_7]: 288500, + [GasGroup.REGISTER_VALIDATOR_WITHOUT_DEPOSIT_7]: 251000, - [GasGroup.BULK_REGISTER_10_VALIDATOR_NEW_STATE_7]: 891900, - [GasGroup.BULK_REGISTER_10_VALIDATOR_EXISTING_CLUSTER_7]: 875200, + [GasGroup.BULK_REGISTER_10_VALIDATOR_NEW_STATE_7]: 890000, + [GasGroup.BULK_REGISTER_10_VALIDATOR_EXISTING_CLUSTER_7]: 873500, - [GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER_10]: 343600, - [GasGroup.REGISTER_VALIDATOR_NEW_STATE_10]: 360300, - [GasGroup.REGISTER_VALIDATOR_WITHOUT_DEPOSIT_10]: 323000, + [GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER_10]: 342000, + [GasGroup.REGISTER_VALIDATOR_NEW_STATE_10]: 359000, + [GasGroup.REGISTER_VALIDATOR_WITHOUT_DEPOSIT_10]: 321500, - [GasGroup.BULK_REGISTER_10_VALIDATOR_NEW_STATE_10]: 952159, - [GasGroup.BULK_REGISTER_10_VALIDATOR_EXISTING_CLUSTER_10]: 935500, + [GasGroup.BULK_REGISTER_10_VALIDATOR_NEW_STATE_10]: 950500, + [GasGroup.BULK_REGISTER_10_VALIDATOR_EXISTING_CLUSTER_10]: 933500, - [GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER_13]: 414500, - [GasGroup.REGISTER_VALIDATOR_NEW_STATE_13]: 431300, - [GasGroup.REGISTER_VALIDATOR_WITHOUT_DEPOSIT_13]: 394000, + [GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER_13]: 413000, + [GasGroup.REGISTER_VALIDATOR_NEW_STATE_13]: 430000, + [GasGroup.REGISTER_VALIDATOR_WITHOUT_DEPOSIT_13]: 392500, [GasGroup.BULK_REGISTER_10_VALIDATOR_NEW_STATE_13]: 1010500, - [GasGroup.BULK_REGISTER_10_VALIDATOR_EXISTING_CLUSTER_13]: 995706, + [GasGroup.BULK_REGISTER_10_VALIDATOR_EXISTING_CLUSTER_13]: 994000, [GasGroup.REMOVE_VALIDATOR]: 113500, [GasGroup.REMOVE_VALIDATOR_7]: 155000, @@ -114,10 +114,10 @@ const MAX_GAS_PER_GROUP: any = { [GasGroup.WITHDRAW_OPERATOR_BALANCE]: 64900, [GasGroup.VALIDATOR_EXIT]: 42300, - [GasGroup.LIQUIDATE_CLUSTER_4]: 129300, - [GasGroup.LIQUIDATE_CLUSTER_7]: 170500, - [GasGroup.LIQUIDATE_CLUSTER_10]: 211600, - [GasGroup.LIQUIDATE_CLUSTER_13]: 252800, + [GasGroup.LIQUIDATE_CLUSTER_4]: 129300, + [GasGroup.LIQUIDATE_CLUSTER_7]: 170500, + [GasGroup.LIQUIDATE_CLUSTER_10]: 211600, + [GasGroup.LIQUIDATE_CLUSTER_13]: 252800, [GasGroup.REACTIVATE_CLUSTER]: 120600, [GasGroup.NETWORK_FEE_CHANGE]: 45800, @@ -130,7 +130,7 @@ const MAX_GAS_PER_GROUP: any = { [GasGroup.CHANGE_LIQUIDATION_THRESHOLD_PERIOD]: 41000, [GasGroup.CHANGE_MINIMUM_COLLATERAL]: 41200, - + }; class GasStats { diff --git a/test/validators/register.ts b/test/validators/register.ts index c5a936cb..b29d58ab 100644 --- a/test/validators/register.ts +++ b/test/validators/register.ts @@ -3,7 +3,6 @@ import * as helpers from '../helpers/contract-helpers'; import * as utils from '../helpers/utils'; import { expect } from 'chai'; import { trackGas, GasGroup } from '../helpers/gas-usage'; -import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; let ssvNetworkContract: any, ssvViews: any, ssvToken: any, minDepositAmount: any, cluster1: any; @@ -398,7 +397,7 @@ describe('Register Validator Tests', () => { balance: 0, active: true } - ), [GasGroup.BULK_REGISTER_10_VALIDATOR_NEW_STATE_4]); + ), [GasGroup.BULK_REGISTER_10_VALIDATOR_NEW_STATE_7]); }); // 10 operators