// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.24; import "../interfaces/ISSVNetworkCore.sol"; import {StorageData} from "./SSVStorage.sol"; import {StorageProtocol} from "./SSVStorageProtocol.sol"; import "./OperatorLib.sol"; import "./ProtocolLib.sol"; import {Types64} from "./Types.sol"; library ClusterLib { using Types64 for uint64; using ProtocolLib for StorageProtocol; function updateBalance( ISSVNetworkCore.Cluster memory cluster, uint64 newIndex, uint64 currentNetworkFeeIndex ) internal pure { uint64 networkFee = uint64(currentNetworkFeeIndex - cluster.networkFeeIndex) * cluster.validatorCount; uint64 usage = (newIndex - cluster.index) * cluster.validatorCount + networkFee; cluster.balance = usage.expand() > cluster.balance ? 0 : cluster.balance - usage.expand(); } function isLiquidatable( ISSVNetworkCore.Cluster memory cluster, uint64 burnRate, uint64 networkFee, uint64 minimumBlocksBeforeLiquidation, uint64 minimumLiquidationCollateral ) internal pure returns (bool liquidatable) { if (cluster.validatorCount != 0) { if (cluster.balance < minimumLiquidationCollateral.expand()) return true; uint64 liquidationThreshold = minimumBlocksBeforeLiquidation * (burnRate + networkFee) * cluster.validatorCount; return cluster.balance < liquidationThreshold.expand(); } } function validateClusterIsNotLiquidated(ISSVNetworkCore.Cluster memory cluster) internal pure { if (!cluster.active) revert ISSVNetworkCore.ClusterIsLiquidated(); } function validateHashedCluster( ISSVNetworkCore.Cluster memory cluster, address owner, uint64[] memory operatorIds, StorageData storage s ) internal view returns (bytes32 hashedCluster) { hashedCluster = keccak256(abi.encodePacked(owner, operatorIds)); bytes32 hashedClusterData = hashClusterData(cluster); bytes32 clusterData = s.clusters[hashedCluster]; if (clusterData == bytes32(0)) { revert ISSVNetworkCore.ClusterDoesNotExists(); } else if (clusterData != hashedClusterData) { revert ISSVNetworkCore.IncorrectClusterState(); } } function updateClusterData( ISSVNetworkCore.Cluster memory cluster, uint64 clusterIndex, uint64 currentNetworkFeeIndex ) internal pure { updateBalance(cluster, clusterIndex, currentNetworkFeeIndex); cluster.index = clusterIndex; cluster.networkFeeIndex = currentNetworkFeeIndex; } function hashClusterData(ISSVNetworkCore.Cluster memory cluster) internal pure returns (bytes32) { return keccak256( abi.encodePacked( cluster.validatorCount, cluster.networkFeeIndex, cluster.index, cluster.balance, cluster.active ) ); } 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 clusterIndex, uint64 burnRate) = OperatorLib.updateClusterOperatorsOnRegistration( operatorIds, 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); } }