// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.24; import {ISSVOperators} from "../interfaces/ISSVOperators.sol"; import {Types64, Types256} from "../libraries/Types.sol"; import {SSVStorage, StorageData} from "../libraries/SSVStorage.sol"; import {SSVStorageProtocol, StorageProtocol} from "../libraries/SSVStorageProtocol.sol"; import "../libraries/OperatorLib.sol"; import "../libraries/CoreLib.sol"; import {Counters} from "@openzeppelin/contracts/utils/Counters.sol"; contract SSVOperators is ISSVOperators { uint64 private constant MINIMAL_OPERATOR_FEE = 1_000_000_000; uint64 private constant PRECISION_FACTOR = 10_000; using Types256 for uint256; using Types64 for uint64; using Counters for Counters.Counter; using OperatorLib for Operator; /*******************************/ /* Operator External Functions */ /*******************************/ function registerOperator( bytes calldata publicKey, uint256 fee, bool setPrivate ) external override returns (uint64 id) { if (fee != 0 && fee < MINIMAL_OPERATOR_FEE) { revert ISSVNetworkCore.FeeTooLow(); } if (fee > SSVStorageProtocol.load().operatorMaxFee) { revert ISSVNetworkCore.FeeTooHigh(); } StorageData storage s = SSVStorage.load(); bytes32 hashedPk = keccak256(publicKey); if (s.operatorsPKs[hashedPk] != 0) revert ISSVNetworkCore.OperatorAlreadyExists(); s.lastOperatorId.increment(); id = uint64(s.lastOperatorId.current()); s.operators[id] = Operator({ owner: msg.sender, snapshot: ISSVNetworkCore.Snapshot({block: uint32(block.number), index: 0, balance: 0}), validatorCount: 0, fee: fee.shrink(), whitelisted: setPrivate }); s.operatorsPKs[hashedPk] = id; uint64[] memory operatorIds = new uint64[](1); operatorIds[0] = id; emit OperatorAdded(id, msg.sender, publicKey, fee); emit OperatorPrivacyStatusUpdated(operatorIds, setPrivate); } function removeOperator(uint64 operatorId) external override { StorageData storage s = SSVStorage.load(); Operator memory operator = s.operators[operatorId]; operator.checkOwner(); operator.updateSnapshot(); uint64 currentBalance = operator.snapshot.balance; operator.snapshot.block = 0; operator.snapshot.balance = 0; operator.validatorCount = 0; operator.fee = 0; s.operators[operatorId] = operator; delete s.operatorsWhitelist[operatorId]; if (currentBalance > 0) { _transferOperatorBalanceUnsafe(operatorId, currentBalance.expand()); } emit OperatorRemoved(operatorId); } function declareOperatorFee(uint64 operatorId, uint256 fee) external override { StorageData storage s = SSVStorage.load(); s.operators[operatorId].checkOwner(); StorageProtocol storage sp = SSVStorageProtocol.load(); if (fee != 0 && fee < MINIMAL_OPERATOR_FEE) revert FeeTooLow(); if (fee > sp.operatorMaxFee) revert FeeTooHigh(); uint64 operatorFee = s.operators[operatorId].fee; uint64 shrunkFee = fee.shrink(); if (operatorFee == shrunkFee) { revert SameFeeChangeNotAllowed(); } else if (shrunkFee != 0 && operatorFee == 0) { revert FeeIncreaseNotAllowed(); } // @dev 100% = 10000, 10% = 1000 - using 10000 to represent 2 digit precision uint64 maxAllowedFee = (operatorFee * (PRECISION_FACTOR + sp.operatorMaxFeeIncrease)) / PRECISION_FACTOR; if (shrunkFee > maxAllowedFee) revert FeeExceedsIncreaseLimit(); s.operatorFeeChangeRequests[operatorId] = OperatorFeeChangeRequest( shrunkFee, uint64(block.timestamp) + sp.declareOperatorFeePeriod, uint64(block.timestamp) + sp.declareOperatorFeePeriod + sp.executeOperatorFeePeriod ); emit OperatorFeeDeclared(msg.sender, operatorId, block.number, fee); } function executeOperatorFee(uint64 operatorId) external override { StorageData storage s = SSVStorage.load(); Operator memory operator = s.operators[operatorId]; operator.checkOwner(); OperatorFeeChangeRequest memory feeChangeRequest = s.operatorFeeChangeRequests[operatorId]; if (feeChangeRequest.approvalBeginTime == 0) revert NoFeeDeclared(); if ( block.timestamp < feeChangeRequest.approvalBeginTime || block.timestamp > feeChangeRequest.approvalEndTime ) { revert ApprovalNotWithinTimeframe(); } if (feeChangeRequest.fee.expand() > SSVStorageProtocol.load().operatorMaxFee) revert FeeTooHigh(); operator.updateSnapshot(); operator.fee = feeChangeRequest.fee; s.operators[operatorId] = operator; delete s.operatorFeeChangeRequests[operatorId]; emit OperatorFeeExecuted(msg.sender, operatorId, block.number, feeChangeRequest.fee.expand()); } function cancelDeclaredOperatorFee(uint64 operatorId) external override { StorageData storage s = SSVStorage.load(); s.operators[operatorId].checkOwner(); if (s.operatorFeeChangeRequests[operatorId].approvalBeginTime == 0) revert NoFeeDeclared(); delete s.operatorFeeChangeRequests[operatorId]; emit OperatorFeeDeclarationCancelled(msg.sender, operatorId); } function reduceOperatorFee(uint64 operatorId, uint256 fee) external override { StorageData storage s = SSVStorage.load(); Operator memory operator = s.operators[operatorId]; operator.checkOwner(); if (fee != 0 && fee < MINIMAL_OPERATOR_FEE) revert FeeTooLow(); uint64 shrunkAmount = fee.shrink(); if (shrunkAmount >= operator.fee) revert FeeIncreaseNotAllowed(); operator.updateSnapshot(); operator.fee = shrunkAmount; s.operators[operatorId] = operator; delete s.operatorFeeChangeRequests[operatorId]; emit OperatorFeeExecuted(msg.sender, operatorId, block.number, fee); } function setOperatorsPrivateUnchecked(uint64[] calldata operatorIds) external override { OperatorLib.updatePrivacyStatus(operatorIds, true, SSVStorage.load()); emit OperatorPrivacyStatusUpdated(operatorIds, true); } function setOperatorsPublicUnchecked(uint64[] calldata operatorIds) external override { OperatorLib.updatePrivacyStatus(operatorIds, false, SSVStorage.load()); emit OperatorPrivacyStatusUpdated(operatorIds, false); } function withdrawOperatorEarnings(uint64 operatorId, uint256 amount) external override { _withdrawOperatorEarnings(operatorId, amount); } function withdrawAllOperatorEarnings(uint64 operatorId) external override { _withdrawOperatorEarnings(operatorId, 0); } // private functions function _withdrawOperatorEarnings(uint64 operatorId, uint256 amount) private { StorageData storage s = SSVStorage.load(); Operator memory operator = s.operators[operatorId]; operator.checkOwner(); operator.updateSnapshot(); uint64 shrunkWithdrawn; uint64 shrunkAmount = amount.shrink(); if (amount == 0 && operator.snapshot.balance > 0) { shrunkWithdrawn = operator.snapshot.balance; } else if (amount > 0 && operator.snapshot.balance >= shrunkAmount) { shrunkWithdrawn = shrunkAmount; } else { revert InsufficientBalance(); } operator.snapshot.balance -= shrunkWithdrawn; s.operators[operatorId] = operator; _transferOperatorBalanceUnsafe(operatorId, shrunkWithdrawn.expand()); } function _transferOperatorBalanceUnsafe(uint64 operatorId, uint256 amount) private { CoreLib.transferBalance(msg.sender, amount); emit OperatorWithdrawn(msg.sender, operatorId, amount); } }