diff --git a/docs/storage-report/RewardsCoordinator.md b/docs/storage-report/RewardsCoordinator.md index 3c14eea58..113781b56 100644 --- a/docs/storage-report/RewardsCoordinator.md +++ b/docs/storage-report/RewardsCoordinator.md @@ -1,59 +1,63 @@ -╭--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------╮ -| Name | Type | Slot | Offset | Bytes | Contract | -+===========================================================================================================================================================================================================================+ -| _initialized | uint8 | 0 | 0 | 1 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| _initializing | bool | 0 | 1 | 1 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| __gap | uint256[50] | 1 | 0 | 1600 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| _owner | address | 51 | 0 | 20 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| __gap | uint256[49] | 52 | 0 | 1568 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| __deprecated_pauserRegistry | contract IPauserRegistry | 101 | 0 | 20 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| _paused | uint256 | 102 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| __gap | uint256[48] | 103 | 0 | 1536 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| _status | uint256 | 151 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| __gap | uint256[49] | 152 | 0 | 1568 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| __deprecated_DOMAIN_SEPARATOR | bytes32 | 201 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| _distributionRoots | struct IRewardsCoordinatorTypes.DistributionRoot[] | 202 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| rewardsUpdater | address | 203 | 0 | 20 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| activationDelay | uint32 | 203 | 20 | 4 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| currRewardsCalculationEndTimestamp | uint32 | 203 | 24 | 4 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| defaultOperatorSplitBips | uint16 | 203 | 28 | 2 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| claimerFor | mapping(address => address) | 204 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| cumulativeClaimed | mapping(address => mapping(contract IERC20 => uint256)) | 205 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| submissionNonce | mapping(address => uint256) | 206 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| isAVSRewardsSubmissionHash | mapping(address => mapping(bytes32 => bool)) | 207 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| isRewardsSubmissionForAllHash | mapping(address => mapping(bytes32 => bool)) | 208 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| isRewardsForAllSubmitter | mapping(address => bool) | 209 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| isRewardsSubmissionForAllEarnersHash | mapping(address => mapping(bytes32 => bool)) | 210 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| isOperatorDirectedAVSRewardsSubmissionHash | mapping(address => mapping(bytes32 => bool)) | 211 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| operatorAVSSplitBips | mapping(address => mapping(address => struct IRewardsCoordinatorTypes.OperatorSplit)) | 212 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| operatorPISplitBips | mapping(address => struct IRewardsCoordinatorTypes.OperatorSplit) | 213 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| -| __gap | uint256[37] | 214 | 0 | 1184 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | -╰--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------╯ +╭-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------╮ +| Name | Type | Slot | Offset | Bytes | Contract | ++==============================================================================================================================================================================================================================+ +| _initialized | uint8 | 0 | 0 | 1 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| _initializing | bool | 0 | 1 | 1 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| __gap | uint256[50] | 1 | 0 | 1600 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| _owner | address | 51 | 0 | 20 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| __gap | uint256[49] | 52 | 0 | 1568 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| __deprecated_pauserRegistry | contract IPauserRegistry | 101 | 0 | 20 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| _paused | uint256 | 102 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| __gap | uint256[48] | 103 | 0 | 1536 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| _status | uint256 | 151 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| __gap | uint256[49] | 152 | 0 | 1568 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| __deprecated_DOMAIN_SEPARATOR | bytes32 | 201 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| _distributionRoots | struct IRewardsCoordinatorTypes.DistributionRoot[] | 202 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| rewardsUpdater | address | 203 | 0 | 20 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| activationDelay | uint32 | 203 | 20 | 4 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| currRewardsCalculationEndTimestamp | uint32 | 203 | 24 | 4 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| defaultOperatorSplitBips | uint16 | 203 | 28 | 2 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| claimerFor | mapping(address => address) | 204 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| cumulativeClaimed | mapping(address => mapping(contract IERC20 => uint256)) | 205 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| submissionNonce | mapping(address => uint256) | 206 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| isAVSRewardsSubmissionHash | mapping(address => mapping(bytes32 => bool)) | 207 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| isRewardsSubmissionForAllHash | mapping(address => mapping(bytes32 => bool)) | 208 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| isRewardsForAllSubmitter | mapping(address => bool) | 209 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| isRewardsSubmissionForAllEarnersHash | mapping(address => mapping(bytes32 => bool)) | 210 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| isOperatorDirectedAVSRewardsSubmissionHash | mapping(address => mapping(bytes32 => bool)) | 211 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| isOperatorSetPerformanceRewardsSubmissionHash | mapping(address => mapping(bytes32 => bool)) | 212 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| _operatorAVSSplitBips | mapping(address => mapping(address => struct IRewardsCoordinatorTypes.OperatorSplit)) | 213 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| _operatorPISplitBips | mapping(address => struct IRewardsCoordinatorTypes.OperatorSplit) | 214 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| _operatorOperatorSetSplitBips | mapping(address => mapping(bytes32 => struct IRewardsCoordinatorTypes.OperatorSplit)) | 215 | 0 | 32 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------| +| __gap | uint256[37] | 216 | 0 | 1184 | src/contracts/core/RewardsCoordinator.sol:RewardsCoordinator | +╰-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+--------------------------------------------------------------╯ diff --git a/docs/storage-report/RewardsCoordinatorStorage.md b/docs/storage-report/RewardsCoordinatorStorage.md index 9aa44693d..5ffb2e442 100644 --- a/docs/storage-report/RewardsCoordinatorStorage.md +++ b/docs/storage-report/RewardsCoordinatorStorage.md @@ -1,39 +1,43 @@ -╭--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------╮ -| Name | Type | Slot | Offset | Bytes | Contract | -+=========================================================================================================================================================================================================================================+ -| __deprecated_DOMAIN_SEPARATOR | bytes32 | 0 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| _distributionRoots | struct IRewardsCoordinatorTypes.DistributionRoot[] | 1 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| rewardsUpdater | address | 2 | 0 | 20 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| activationDelay | uint32 | 2 | 20 | 4 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| currRewardsCalculationEndTimestamp | uint32 | 2 | 24 | 4 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| defaultOperatorSplitBips | uint16 | 2 | 28 | 2 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| claimerFor | mapping(address => address) | 3 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| cumulativeClaimed | mapping(address => mapping(contract IERC20 => uint256)) | 4 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| submissionNonce | mapping(address => uint256) | 5 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| isAVSRewardsSubmissionHash | mapping(address => mapping(bytes32 => bool)) | 6 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| isRewardsSubmissionForAllHash | mapping(address => mapping(bytes32 => bool)) | 7 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| isRewardsForAllSubmitter | mapping(address => bool) | 8 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| isRewardsSubmissionForAllEarnersHash | mapping(address => mapping(bytes32 => bool)) | 9 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| isOperatorDirectedAVSRewardsSubmissionHash | mapping(address => mapping(bytes32 => bool)) | 10 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| operatorAVSSplitBips | mapping(address => mapping(address => struct IRewardsCoordinatorTypes.OperatorSplit)) | 11 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| operatorPISplitBips | mapping(address => struct IRewardsCoordinatorTypes.OperatorSplit) | 12 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -|--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| -| __gap | uint256[37] | 13 | 0 | 1184 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | -╰--------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------╯ +╭-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------╮ +| Name | Type | Slot | Offset | Bytes | Contract | ++============================================================================================================================================================================================================================================+ +| __deprecated_DOMAIN_SEPARATOR | bytes32 | 0 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| _distributionRoots | struct IRewardsCoordinatorTypes.DistributionRoot[] | 1 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| rewardsUpdater | address | 2 | 0 | 20 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| activationDelay | uint32 | 2 | 20 | 4 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| currRewardsCalculationEndTimestamp | uint32 | 2 | 24 | 4 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| defaultOperatorSplitBips | uint16 | 2 | 28 | 2 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| claimerFor | mapping(address => address) | 3 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| cumulativeClaimed | mapping(address => mapping(contract IERC20 => uint256)) | 4 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| submissionNonce | mapping(address => uint256) | 5 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| isAVSRewardsSubmissionHash | mapping(address => mapping(bytes32 => bool)) | 6 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| isRewardsSubmissionForAllHash | mapping(address => mapping(bytes32 => bool)) | 7 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| isRewardsForAllSubmitter | mapping(address => bool) | 8 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| isRewardsSubmissionForAllEarnersHash | mapping(address => mapping(bytes32 => bool)) | 9 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| isOperatorDirectedAVSRewardsSubmissionHash | mapping(address => mapping(bytes32 => bool)) | 10 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| isOperatorSetPerformanceRewardsSubmissionHash | mapping(address => mapping(bytes32 => bool)) | 11 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| _operatorAVSSplitBips | mapping(address => mapping(address => struct IRewardsCoordinatorTypes.OperatorSplit)) | 12 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| _operatorPISplitBips | mapping(address => struct IRewardsCoordinatorTypes.OperatorSplit) | 13 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| _operatorOperatorSetSplitBips | mapping(address => mapping(bytes32 => struct IRewardsCoordinatorTypes.OperatorSplit)) | 14 | 0 | 32 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +|-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------| +| __gap | uint256[37] | 15 | 0 | 1184 | src/contracts/core/RewardsCoordinatorStorage.sol:RewardsCoordinatorStorage | +╰-----------------------------------------------+---------------------------------------------------------------------------------------+------+--------+-------+----------------------------------------------------------------------------╯ diff --git a/src/contracts/core/RewardsCoordinator.sol b/src/contracts/core/RewardsCoordinator.sol index 13e2569d5..a16409951 100644 --- a/src/contracts/core/RewardsCoordinator.sol +++ b/src/contracts/core/RewardsCoordinator.sol @@ -29,6 +29,7 @@ contract RewardsCoordinator is PermissionControllerMixin { using SafeERC20 for IERC20; + using OperatorSetLib for OperatorSet; modifier onlyRewardsUpdater() { require(msg.sender == rewardsUpdater, UnauthorizedCaller()); @@ -177,6 +178,34 @@ contract RewardsCoordinator is } } + /// @inheritdoc IRewardsCoordinator + function createOperatorDirectedOperatorSetRewardsSubmission( + OperatorSet calldata operatorSet, + OperatorDirectedRewardsSubmission[] calldata rewardsSubmissions + ) + external + onlyWhenNotPaused(PAUSED_OPERATOR_DIRECTED_OPERATOR_SET_REWARDS_SUBMISSION) + checkCanCall(operatorSet.avs) + nonReentrant + { + require(allocationManager.isOperatorSet(operatorSet), InvalidOperatorSet()); + for (uint256 i = 0; i < rewardsSubmissions.length; i++) { + OperatorDirectedRewardsSubmission calldata rewardsSubmission = rewardsSubmissions[i]; + uint256 nonce = submissionNonce[operatorSet.avs]; + bytes32 rewardsSubmissionHash = keccak256(abi.encode(operatorSet.avs, nonce, rewardsSubmission)); + + uint256 totalAmount = _validateOperatorDirectedRewardsSubmission(rewardsSubmission); + + isOperatorSetPerformanceRewardsSubmissionHash[operatorSet.avs][rewardsSubmissionHash] = true; + submissionNonce[operatorSet.avs] = nonce + 1; + + emit OperatorDirectedOperatorSetRewardsSubmissionCreated( + msg.sender, operatorSet, rewardsSubmissionHash, nonce, rewardsSubmission + ); + rewardsSubmission.token.safeTransferFrom(msg.sender, address(this), totalAmount); + } + } + /// @inheritdoc IRewardsCoordinator function processClaim( RewardsMerkleClaim calldata claim, @@ -268,8 +297,8 @@ contract RewardsCoordinator is uint16 split ) external onlyWhenNotPaused(PAUSED_OPERATOR_AVS_SPLIT) checkCanCall(operator) { uint32 activatedAt = uint32(block.timestamp) + activationDelay; - uint16 oldSplit = _getOperatorSplit(operatorAVSSplitBips[operator][avs]); - _setOperatorSplit(operatorAVSSplitBips[operator][avs], split, activatedAt); + uint16 oldSplit = _getOperatorSplit(_operatorAVSSplitBips[operator][avs]); + _setOperatorSplit(_operatorAVSSplitBips[operator][avs], split, activatedAt); emit OperatorAVSSplitBipsSet(msg.sender, operator, avs, activatedAt, oldSplit, split); } @@ -280,12 +309,27 @@ contract RewardsCoordinator is uint16 split ) external onlyWhenNotPaused(PAUSED_OPERATOR_PI_SPLIT) checkCanCall(operator) { uint32 activatedAt = uint32(block.timestamp) + activationDelay; - uint16 oldSplit = _getOperatorSplit(operatorPISplitBips[operator]); - _setOperatorSplit(operatorPISplitBips[operator], split, activatedAt); + uint16 oldSplit = _getOperatorSplit(_operatorPISplitBips[operator]); + _setOperatorSplit(_operatorPISplitBips[operator], split, activatedAt); emit OperatorPISplitBipsSet(msg.sender, operator, activatedAt, oldSplit, split); } + /// @inheritdoc IRewardsCoordinator + function setOperatorSetSplit( + address operator, + OperatorSet calldata operatorSet, + uint16 split + ) external onlyWhenNotPaused(PAUSED_OPERATOR_SET_OPERATOR_SPLIT) checkCanCall(operator) { + require(allocationManager.isOperatorSet(operatorSet), InvalidOperatorSet()); + + uint32 activatedAt = uint32(block.timestamp) + activationDelay; + uint16 oldSplit = _getOperatorSplit(_operatorOperatorSetSplitBips[operator][operatorSet.key()]); + _setOperatorSplit(_operatorOperatorSetSplitBips[operator][operatorSet.key()], split, activatedAt); + + emit OperatorOperatorSetSplitBipsSet(msg.sender, operator, operatorSet, activatedAt, oldSplit, split); + } + /// @inheritdoc IRewardsCoordinator function setRewardsUpdater( address _rewardsUpdater @@ -602,14 +646,19 @@ contract RewardsCoordinator is /// @inheritdoc IRewardsCoordinator function getOperatorAVSSplit(address operator, address avs) external view returns (uint16) { - return _getOperatorSplit(operatorAVSSplitBips[operator][avs]); + return _getOperatorSplit(_operatorAVSSplitBips[operator][avs]); } /// @inheritdoc IRewardsCoordinator function getOperatorPISplit( address operator ) external view returns (uint16) { - return _getOperatorSplit(operatorPISplitBips[operator]); + return _getOperatorSplit(_operatorPISplitBips[operator]); + } + + /// @inheritdoc IRewardsCoordinator + function getOperatorSetSplit(address operator, OperatorSet calldata operatorSet) external view returns (uint16) { + return _getOperatorSplit(_operatorOperatorSetSplitBips[operator][operatorSet.key()]); } /// @inheritdoc IRewardsCoordinator diff --git a/src/contracts/core/RewardsCoordinatorStorage.sol b/src/contracts/core/RewardsCoordinatorStorage.sol index 6c519214f..edc9c9522 100644 --- a/src/contracts/core/RewardsCoordinatorStorage.sol +++ b/src/contracts/core/RewardsCoordinatorStorage.sol @@ -27,10 +27,14 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator { uint8 internal constant PAUSED_REWARD_ALL_STAKERS_AND_OPERATORS = 4; /// @dev Index for flag that pauses calling createOperatorDirectedAVSRewardsSubmission uint8 internal constant PAUSED_OPERATOR_DIRECTED_AVS_REWARDS_SUBMISSION = 5; + /// @dev Index for flag that pauses calling setOperatorSetPerformanceRewardsSubmission + uint8 internal constant PAUSED_OPERATOR_DIRECTED_OPERATOR_SET_REWARDS_SUBMISSION = 6; /// @dev Index for flag that pauses calling setOperatorAVSSplit - uint8 internal constant PAUSED_OPERATOR_AVS_SPLIT = 6; + uint8 internal constant PAUSED_OPERATOR_AVS_SPLIT = 7; /// @dev Index for flag that pauses calling setOperatorPISplit - uint8 internal constant PAUSED_OPERATOR_PI_SPLIT = 7; + uint8 internal constant PAUSED_OPERATOR_PI_SPLIT = 8; + /// @dev Index for flag that pauses calling setOperatorSetSplit + uint8 internal constant PAUSED_OPERATOR_SET_OPERATOR_SPLIT = 9; /// @dev Salt for the earner leaf, meant to distinguish from tokenLeaf since they have the same sized data uint8 internal constant EARNER_LEAF_SALT = 0; @@ -115,14 +119,21 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator { // Construction - /// @notice Mapping: avs => operatorDirectedAVSRewardsSubmissionHash => bool to check if operator-directed rewards submission hash has been submitted - mapping(address => mapping(bytes32 => bool)) public isOperatorDirectedAVSRewardsSubmissionHash; + /// @notice Returns whether a `hash` is a `valid` operator set performance rewards submission hash for a given `avs`. + mapping(address avs => mapping(bytes32 hash => bool valid)) public isOperatorDirectedAVSRewardsSubmissionHash; - /// @notice Mapping: operator => avs => OperatorSplit. The split an operator takes for a specific AVS. - mapping(address => mapping(address => OperatorSplit)) internal operatorAVSSplitBips; + /// @notice Returns whether a `hash` is a `valid` operator set performance rewards submission hash for a given `avs`. + mapping(address avs => mapping(bytes32 hash => bool valid)) public isOperatorSetPerformanceRewardsSubmissionHash; - /// @notice Mapping: operator => OperatorPISplit. The split an operator takes for Programmatic Incentives. - mapping(address => OperatorSplit) internal operatorPISplitBips; + /// @notice Returns the `split` an `operator` takes for an `avs`. + mapping(address operator => mapping(address avs => OperatorSplit split)) internal _operatorAVSSplitBips; + + /// @notice Returns the `split` an `operator` takes for Programmatic Incentives. + mapping(address operator => OperatorSplit split) internal _operatorPISplitBips; + + /// @notice Returns the `split` an `operator` takes for a given operator set. + mapping(address operator => mapping(bytes32 operatorSetKey => OperatorSplit split)) internal + _operatorOperatorSetSplitBips; constructor( IDelegationManager _delegationManager, @@ -153,5 +164,5 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[37] private __gap; + uint256[35] private __gap; } diff --git a/src/contracts/interfaces/IRewardsCoordinator.sol b/src/contracts/interfaces/IRewardsCoordinator.sol index c019c8efe..2c94435e2 100644 --- a/src/contracts/interfaces/IRewardsCoordinator.sol +++ b/src/contracts/interfaces/IRewardsCoordinator.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.27; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "../libraries/OperatorSetLib.sol"; import "./IPauserRegistry.sol"; import "./IStrategy.sol"; @@ -27,6 +28,8 @@ interface IRewardsCoordinatorErrors { error NewRootMustBeForNewCalculatedPeriod(); /// @dev Thrown when rewards end timestamp has not elapsed. error RewardsEndTimestampNotElapsed(); + /// @dev Thrown when an invalid operator set is provided. + error InvalidOperatorSet(); /// Rewards Submissions @@ -277,6 +280,22 @@ interface IRewardsCoordinatorEvents is IRewardsCoordinatorTypes { OperatorDirectedRewardsSubmission operatorDirectedRewardsSubmission ); + /** + * @notice Emitted when an AVS creates a valid performance based `OperatorDirectedRewardsSubmission` + * @param caller The address calling `createOperatorDirectedOperatorSetRewardsSubmission`. + * @param operatorSet The operatorSet on behalf of which the performance rewards are being submitted. + * @param performanceRewardsSubmissionHash Keccak256 hash of (`avs`, `submissionNonce` and `performanceRewardsSubmission`). + * @param submissionNonce Current nonce of the avs. Used to generate a unique submission hash. + * @param performanceRewardsSubmission The Performance Rewards Submission. Contains the token, start timestamp, duration, description and, strategy and multipliers. + */ + event OperatorDirectedOperatorSetRewardsSubmissionCreated( + address indexed caller, + OperatorSet indexed operatorSet, + bytes32 indexed performanceRewardsSubmissionHash, + uint256 submissionNonce, + OperatorDirectedRewardsSubmission performanceRewardsSubmission + ); + /// @notice rewardsUpdater is responsible for submiting DistributionRoots, only owner can set rewardsUpdater event RewardsUpdaterSet(address indexed oldRewardsUpdater, address indexed newRewardsUpdater); @@ -321,6 +340,24 @@ interface IRewardsCoordinatorEvents is IRewardsCoordinatorTypes { uint16 newOperatorPISplitBips ); + /** + * @notice Emitted when the operator split for a given operatorSet is set. + * @param caller The address calling `setOperatorSetSplit`. + * @param operator The operator on behalf of which the split is being set. + * @param operatorSet The operatorSet for which the split is being set. + * @param activatedAt The timestamp at which the split will be activated. + * @param oldOperatorSetSplitBips The old split for the operator for the operatorSet. + * @param newOperatorSetSplitBips The new split for the operator for the operatorSet. + */ + event OperatorOperatorSetSplitBipsSet( + address indexed caller, + address indexed operator, + OperatorSet indexed operatorSet, + uint32 activatedAt, + uint16 oldOperatorSetSplitBips, + uint16 newOperatorSetSplitBips + ); + event ClaimerForSet(address indexed earner, address indexed oldClaimer, address indexed claimer); /// @notice rootIndex is the specific array index of the newly created root in the storage array @@ -420,6 +457,13 @@ interface IRewardsCoordinator is IRewardsCoordinatorErrors, IRewardsCoordinatorE OperatorDirectedRewardsSubmission[] calldata operatorDirectedRewardsSubmissions ) external; + /// @notice operatorSet parallel of createAVSPerformanceRewardsSubmission + /// @dev sender must be the avs of the given operatorSet + function createOperatorDirectedOperatorSetRewardsSubmission( + OperatorSet calldata operatorSet, + OperatorDirectedRewardsSubmission[] calldata performanceRewardsSubmissions + ) external; + /** * @notice Claim rewards against a given root (read from _distributionRoots[claim.rootIndex]). * Earnings are cumulative so earners don't have to claim against all distribution roots they have earnings for, @@ -522,6 +566,14 @@ interface IRewardsCoordinator is IRewardsCoordinatorErrors, IRewardsCoordinatorE */ function setOperatorPISplit(address operator, uint16 split) external; + /** + * @notice Sets the split for a specific operator for a specific operatorSet. + * @param operator The operator who is setting the split. + * @param operatorSet The operatorSet for which the split is being set by the operator. + * @param split The split for the operator for the specific operatorSet in bips. + */ + function setOperatorSetSplit(address operator, OperatorSet calldata operatorSet, uint16 split) external; + /** * @notice Sets the permissioned `rewardsUpdater` address which can post new roots * @dev Only callable by the contract owner @@ -570,6 +622,9 @@ interface IRewardsCoordinator is IRewardsCoordinatorErrors, IRewardsCoordinatorE address operator ) external view returns (uint16); + /// @notice Returns the split for a specific `operator` for a given `operatorSet` + function getOperatorSetSplit(address operator, OperatorSet calldata operatorSet) external view returns (uint16); + /// @notice return the hash of the earner's leaf function calculateEarnerLeafHash( EarnerTreeMerkleLeaf calldata leaf diff --git a/src/test/mocks/AllocationManagerMock.sol b/src/test/mocks/AllocationManagerMock.sol index 9ccb063a0..04d64f52c 100644 --- a/src/test/mocks/AllocationManagerMock.sol +++ b/src/test/mocks/AllocationManagerMock.sol @@ -2,18 +2,29 @@ pragma solidity ^0.8.9; import "forge-std/Test.sol"; -import "../../contracts/interfaces/IStrategy.sol"; -import "../../contracts/libraries/Snapshots.sol"; +import "src/contracts/interfaces/IStrategy.sol"; +import "src/contracts/libraries/Snapshots.sol"; +import "src/contracts/libraries/OperatorSetLib.sol"; contract AllocationManagerMock is Test { using Snapshots for Snapshots.DefaultWadHistory; + using OperatorSetLib for OperatorSet; receive() external payable {} fallback() external payable {} + mapping(bytes32 operatorSetKey => bool) public _isOperatorSet; mapping(address avs => uint256) public getOperatorSetCount; mapping(address => mapping(IStrategy => Snapshots.DefaultWadHistory)) internal _maxMagnitudeHistory; + function setIsOperatorSet(OperatorSet memory operatorSet, bool boolean) external { + _isOperatorSet[operatorSet.key()] = boolean; + } + + function isOperatorSet(OperatorSet memory operatorSet) external view returns (bool) { + return _isOperatorSet[operatorSet.key()]; + } + function setMaxMagnitudes( address operator, IStrategy[] calldata strategies, diff --git a/src/test/unit/RewardsCoordinatorUnit.t.sol b/src/test/unit/RewardsCoordinatorUnit.t.sol index 899d5c0a0..d48c22c24 100644 --- a/src/test/unit/RewardsCoordinatorUnit.t.sol +++ b/src/test/unit/RewardsCoordinatorUnit.t.sol @@ -83,11 +83,17 @@ contract RewardsCoordinatorUnitTests is EigenLayerUnitTestSetup, IRewardsCoordin /// @dev Index for flag that pauses calling createOperatorDirectedAVSRewardsSubmission uint8 internal constant PAUSED_OPERATOR_DIRECTED_AVS_REWARDS_SUBMISSION = 5; + /// @dev Index for flag that pauses calling setOperatorSetPerformanceRewardsSubmission + uint8 internal constant PAUSED_OPERATOR_DIRECTED_OPERATOR_SET_REWARDS_SUBMISSION = 6; + /// @dev Index for flag that pauses calling setOperatorAVSSplit - uint8 internal constant PAUSED_OPERATOR_AVS_SPLIT = 6; + uint8 internal constant PAUSED_OPERATOR_AVS_SPLIT = 7; /// @dev Index for flag that pauses calling setOperatorPISplit - uint8 internal constant PAUSED_OPERATOR_PI_SPLIT = 7; + uint8 internal constant PAUSED_OPERATOR_PI_SPLIT = 8; + + /// @dev Index for flag that pauses calling setOperatorSetSplit + uint8 internal constant PAUSED_OPERATOR_SET_OPERATOR_SPLIT = 9; // RewardsCoordinator entities address rewardsUpdater = address(1000); @@ -780,6 +786,161 @@ contract RewardsCoordinatorUnitTests_setOperatorPISplit is RewardsCoordinatorUni } } +contract RewardsCoordinatorUnitsTests_setOperatorSetSplit is RewardsCoordinatorUnitTests { + OperatorSet operatorSet; + + function setUp() public virtual override { + RewardsCoordinatorUnitTests.setUp(); + operatorSet = OperatorSet(address(this), 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + } + + // Revert when paused + function testFuzz_Revert_WhenPaused(address operator, uint16 split) public filterFuzzedAddressInputs(operator) { + cheats.assume(operator != address(0)); + split = uint16(bound(split, 0, ONE_HUNDRED_IN_BIPS)); + cheats.prank(pauser); + rewardsCoordinator.pause(2 ** PAUSED_OPERATOR_SET_OPERATOR_SPLIT); + + cheats.prank(operator); + cheats.expectRevert(IPausable.CurrentlyPaused.selector); + rewardsCoordinator.setOperatorSetSplit(operator, operatorSet, split); + } + + // Revert when split is greater than 100% + function testFuzz_Revert_WhenSplitGreaterThan100( + address operator, + uint16 split + ) public filterFuzzedAddressInputs(operator) { + cheats.assume(operator != address(0)); + split = uint16(bound(split, ONE_HUNDRED_IN_BIPS + 1, type(uint16).max)); + + cheats.prank(operator); + cheats.expectRevert(SplitExceedsMax.selector); + rewardsCoordinator.setOperatorSetSplit(operator, operatorSet, split); + } + + function testFuzz_setOperatorSetSplit(address operator, uint16 split) public filterFuzzedAddressInputs(operator) { + cheats.assume(operator != address(0)); + + split = uint16(bound(split, 0, ONE_HUNDRED_IN_BIPS)); + uint32 activatedAt = uint32(block.timestamp) + activationDelay; + uint16 oldSplit = rewardsCoordinator.getOperatorSetSplit(operator, operatorSet); + + cheats.expectEmit(true, true, true, true, address(rewardsCoordinator)); + emit OperatorOperatorSetSplitBipsSet(operator, operator, operatorSet, activatedAt, oldSplit, split); + cheats.prank(operator); + rewardsCoordinator.setOperatorSetSplit(operator, operatorSet, split); + + assertEq(oldSplit, rewardsCoordinator.getOperatorSetSplit(operator, operatorSet), "Incorrect Operator split"); + cheats.warp(activatedAt); + assertEq(split, rewardsCoordinator.getOperatorSetSplit(operator, operatorSet), "Incorrect Operator split"); + } + + function testFuzz_setOperatorSetSplit_UAM(address operator, uint16 split) public filterFuzzedAddressInputs(operator) { + cheats.assume(operator != address(0)); + + // Set UAM + cheats.prank(operator); + permissionController.setAppointee( + operator, + defaultAppointee, + address(rewardsCoordinator), + IRewardsCoordinator.setOperatorSetSplit.selector + ); + + split = uint16(bound(split, 0, ONE_HUNDRED_IN_BIPS)); + uint32 activatedAt = uint32(block.timestamp) + activationDelay; + uint16 oldSplit = rewardsCoordinator.getOperatorSetSplit(operator, operatorSet); + + cheats.expectEmit(true, true, true, true, address(rewardsCoordinator)); + emit OperatorOperatorSetSplitBipsSet(defaultAppointee, operator, operatorSet, activatedAt, oldSplit, split); + cheats.prank(defaultAppointee); + rewardsCoordinator.setOperatorSetSplit(operator, operatorSet, split); + + assertEq(oldSplit, rewardsCoordinator.getOperatorSetSplit(operator, operatorSet), "Incorrect Operator split"); + cheats.warp(activatedAt); + assertEq(split, rewardsCoordinator.getOperatorSetSplit(operator, operatorSet), "Incorrect Operator split"); + } + + // Testing that the split has been initialized for the first time. + function testFuzz_setOperatorSetSplitFirstTime( + address operator, + uint16 split + ) public filterFuzzedAddressInputs(operator) { + cheats.assume(operator != address(0)); + split = uint16(bound(split, 0, ONE_HUNDRED_IN_BIPS)); + uint32 activatedAt = uint32(block.timestamp) + activationDelay; + uint16 oldSplit = rewardsCoordinator.getOperatorSetSplit(operator, operatorSet); + assertEq(oldSplit, defaultSplitBips, "Operator split is not Default split before Initialization"); + + cheats.expectEmit(true, true, true, true, address(rewardsCoordinator)); + emit OperatorOperatorSetSplitBipsSet(operator, operator, operatorSet, activatedAt, oldSplit, split); + cheats.prank(operator); + rewardsCoordinator.setOperatorSetSplit(operator, operatorSet, split); + + assertEq(oldSplit, rewardsCoordinator.getOperatorSetSplit(operator, operatorSet), "Incorrect Operator split"); + cheats.warp(activatedAt); + assertEq(split, rewardsCoordinator.getOperatorSetSplit(operator, operatorSet), "Incorrect Operator split"); + } + + // Testing the split setting for a second time prior to the earlier activation. + function testFuzz_Revert_setOperatorSetSplitSecondTimeBeforePriorActivation( + address operator, + uint16 firstSplit, + uint16 secondSplit, + uint32 warpTime + ) public filterFuzzedAddressInputs(operator) { + cheats.assume(operator != address(0)); + firstSplit = uint16(bound(firstSplit, 0, ONE_HUNDRED_IN_BIPS)); + secondSplit = uint16(bound(secondSplit, 0, ONE_HUNDRED_IN_BIPS)); + warpTime = uint32(bound(warpTime, uint32(block.timestamp), uint32(block.timestamp) + activationDelay)); + + // Setting First Split + cheats.prank(operator); + rewardsCoordinator.setOperatorSetSplit(operator, operatorSet, firstSplit); + // Warping to time before activation of First split + cheats.warp(warpTime); + + // Trying to set Second Split + cheats.prank(operator); + cheats.expectRevert(PreviousSplitPending.selector); + rewardsCoordinator.setOperatorSetSplit(operator, operatorSet, secondSplit); + } + + // Testing the split setting for a second time after earlier activation. + function testFuzz_setOperatorSetSplitSecondTimeAfterPriorActivation( + address operator, + uint16 firstSplit, + uint16 secondSplit, + uint32 warpTime + ) public filterFuzzedAddressInputs(operator) { + cheats.assume(operator != address(0)); + firstSplit = uint16(bound(firstSplit, 0, ONE_HUNDRED_IN_BIPS)); + secondSplit = uint16(bound(secondSplit, 0, ONE_HUNDRED_IN_BIPS)); + warpTime = uint32( + bound(warpTime, uint32(block.timestamp) + activationDelay + 1, type(uint32).max - activationDelay) + ); + + // Setting First Split + cheats.prank(operator); + rewardsCoordinator.setOperatorSetSplit(operator, operatorSet, firstSplit); + // Warping to time after activation of First split + cheats.warp(warpTime); + uint32 activatedAt = uint32(block.timestamp) + activationDelay; + + // Setting Second Split + cheats.expectEmit(true, true, true, true, address(rewardsCoordinator)); + emit OperatorOperatorSetSplitBipsSet(operator, operator, operatorSet, activatedAt, firstSplit, secondSplit); + cheats.prank(operator); + rewardsCoordinator.setOperatorSetSplit(operator, operatorSet, secondSplit); + + assertEq(firstSplit, rewardsCoordinator.getOperatorSetSplit(operator, operatorSet), "Incorrect Operator split"); + cheats.warp(activatedAt); + assertEq(secondSplit, rewardsCoordinator.getOperatorSetSplit(operator, operatorSet), "Incorrect Operator split"); + } +} + contract RewardsCoordinatorUnitTests_createAVSRewardsSubmission is RewardsCoordinatorUnitTests { // Revert when paused function test_Revert_WhenPaused() public { @@ -2471,7 +2632,7 @@ contract RewardsCoordinatorUnitTests_createOperatorDirectedAVSRewardsSubmission avs, avs, rewardsSubmissionHash, - currSubmissionNonce, + currSubmissionNonce, operatorDirectedRewardsSubmissions[0] ); rewardsCoordinator.createOperatorDirectedAVSRewardsSubmission(avs, operatorDirectedRewardsSubmissions); @@ -2691,6 +2852,953 @@ contract RewardsCoordinatorUnitTests_createOperatorDirectedAVSRewardsSubmission } } +contract RewardsCoordinatorUnitTests_createOperatorDirectedOperatorSetRewardsSubmission is RewardsCoordinatorUnitTests { + OperatorSet operatorSet; + // used for stack too deep + + struct FuzzOperatorDirectedAVSRewardsSubmission { + address avs; + uint256 startTimestamp; + uint256 duration; + } + + OperatorReward[] defaultOperatorRewards; + + function setUp() public virtual override { + RewardsCoordinatorUnitTests.setUp(); + + address[] memory operators = new address[](3); + operators[0] = makeAddr("operator1"); + operators[1] = makeAddr("operator2"); + operators[2] = makeAddr("operator3"); + operators = _sortAddressArrayAsc(operators); + + defaultOperatorRewards.push(OperatorReward(operators[0], 1e18)); + defaultOperatorRewards.push(OperatorReward(operators[1], 2e18)); + defaultOperatorRewards.push(OperatorReward(operators[2], 3e18)); + + // Set the timestamp to when Rewards v2 will realisticly go out (i.e 6 months) + cheats.warp(GENESIS_REWARDS_TIMESTAMP + 168 days); + operatorSet = OperatorSet(address(this), 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + } + + /// @dev Sort to ensure that the array is in ascending order for addresses + function _sortAddressArrayAsc( + address[] memory arr + ) internal pure returns (address[] memory) { + uint256 l = arr.length; + for (uint256 i = 0; i < l; i++) { + for (uint256 j = i + 1; j < l; j++) { + if (arr[i] > arr[j]) { + address temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } + } + } + return arr; + } + + function _getTotalRewardsAmount( + OperatorReward[] memory operatorRewards + ) internal pure returns (uint256) { + uint256 totalAmount = 0; + for (uint256 i = 0; i < operatorRewards.length; ++i) { + totalAmount += operatorRewards[i].amount; + } + return totalAmount; + } + + // Revert when paused + function test_Revert_WhenPaused() public { + cheats.prank(pauser); + rewardsCoordinator.pause(2 ** PAUSED_OPERATOR_DIRECTED_OPERATOR_SET_REWARDS_SUBMISSION); + + cheats.expectRevert(IPausable.CurrentlyPaused.selector); + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions; + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + } + + // // Revert from reentrancy + // function testFuzz_Revert_WhenReentrancy(uint256 startTimestamp, uint256 duration) public { + // // 1. Bound fuzz inputs to valid ranges and amounts + // duration = bound(duration, 0, MAX_REWARDS_DURATION); + // duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + // startTimestamp = bound( + // startTimestamp, + // uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + // CALCULATION_INTERVAL_SECONDS - + // 1, + // block.timestamp - duration - 1 + // ); + // startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // // 2. Deploy Reenterer + // Reenterer reenterer = new Reenterer(); + + // // 2. Create operator directed rewards submission input param + // OperatorDirectedRewardsSubmission[] + // memory performanceRewardsSubmissions = new OperatorDirectedRewardsSubmission[](1); + // performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + // strategiesAndMultipliers: defaultStrategyAndMultipliers, + // token: IERC20(address(reenterer)), + // operatorRewards: defaultOperatorRewards, + // startTimestamp: uint32(startTimestamp), + // duration: uint32(duration), + // description: "" + // }); + + // address targetToUse = address(rewardsCoordinator); + // uint256 msgValueToUse = 0; + // bytes memory calldataToUse = abi.encodeWithSelector( + // RewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission.selector, + // address(reenterer), + // performanceRewardsSubmissions + // ); + // reenterer.prepare(targetToUse, msgValueToUse, calldataToUse); + + // cheats.prank(address(reenterer)); + // cheats.expectRevert("ReentrancyGuard: reentrant call"); + // rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission( + // address(reenterer), + // performanceRewardsSubmissions + // ); + // } + + // Revert with 0 length strats and multipliers + function testFuzz_Revert_WhenEmptyStratsAndMultipliers( + address avs, + uint256 startTimestamp, + uint256 duration + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, avs); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp - duration - 1 + ); + startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new OperatorDirectedRewardsSubmission[](1); + StrategyAndMultiplier[] memory emptyStratsAndMultipliers; + performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: emptyStratsAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected revert + cheats.prank(avs); + cheats.expectRevert(InputArrayLengthZero.selector); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + } + + // Revert with 0 length operator rewards + function testFuzz_Revert_WhenEmptyOperatorRewards( + address avs, + uint256 startTimestamp, + uint256 duration + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, avs); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp - duration - 1 + ); + startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new OperatorDirectedRewardsSubmission[](1); + OperatorReward[] memory emptyOperatorRewards; + performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: emptyOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected revert + cheats.prank(avs); + cheats.expectRevert(InputArrayLengthZero.selector); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + } + + // Revert when operator is zero address + function testFuzz_Revert_WhenOperatorIsZeroAddress( + address avs, + uint256 startTimestamp, + uint256 duration + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, avs); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp - duration - 1 + ); + startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new OperatorDirectedRewardsSubmission[](1); + defaultOperatorRewards[0].operator = address(0); + performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected revert + cheats.prank(avs); + cheats.expectRevert(InvalidAddressZero.selector); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + } + + // Revert when duplicate operators + function testFuzz_Revert_WhenDuplicateOperators( + address avs, + uint256 startTimestamp, + uint256 duration + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, avs); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp - duration - 1 + ); + startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new OperatorDirectedRewardsSubmission[](1); + OperatorReward[] memory dupOperatorRewards = new OperatorReward[](2); + dupOperatorRewards[0] = defaultOperatorRewards[0]; + dupOperatorRewards[1] = defaultOperatorRewards[0]; + performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: dupOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected revert + cheats.prank(avs); + cheats.expectRevert(OperatorsNotInAscendingOrder.selector); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + } + + // Revert when operator amount is zero + function testFuzz_Revert_WhenOperatorAmountIsZero( + address avs, + uint256 startTimestamp, + uint256 duration + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, avs); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp - duration - 1 + ); + startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new OperatorDirectedRewardsSubmission[](1); + defaultOperatorRewards[0].amount = 0; + performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected revert + cheats.prank(avs); + cheats.expectRevert(AmountIsZero.selector); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + } + + // Revert when operator amount is zero + function testFuzz_Revert_TotalAmountTooLarge( + address avs, + uint256 startTimestamp, + uint256 duration, + uint256 amount + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + // 1. Bound fuzz inputs to valid ranges and amounts + amount = bound(amount, 1e38, type(uint256).max - 5e18); + IERC20 rewardToken = new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, avs); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp - duration - 1 + ); + startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new OperatorDirectedRewardsSubmission[](1); + defaultOperatorRewards[0].amount = amount; + performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected revert + cheats.prank(avs); + cheats.expectRevert(AmountExceedsMax.selector); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + } + + // Revert with exceeding max duration + function testFuzz_Revert_WhenExceedingMaxDuration( + address avs, + uint256 startTimestamp, + uint256 duration + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, avs); + duration = bound(duration, MAX_REWARDS_DURATION + 1, type(uint32).max); + startTimestamp = bound( + startTimestamp, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp + ); + startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new OperatorDirectedRewardsSubmission[](1); + performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected revert + cheats.prank(avs); + cheats.expectRevert(DurationExceedsMax.selector); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + } + + // Revert with invalid interval seconds + function testFuzz_Revert_WhenInvalidIntervalSeconds( + address avs, + uint256 startTimestamp, + uint256 duration + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, avs); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + cheats.assume(duration % CALCULATION_INTERVAL_SECONDS != 0); + startTimestamp = bound( + startTimestamp, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp - duration - 1 + ); + startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new OperatorDirectedRewardsSubmission[](1); + performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected revert + cheats.prank(avs); + cheats.expectRevert(InvalidDurationRemainder.selector); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + } + + // Revert with invalid interval start timestamp + function testFuzz_Revert_WhenInvalidIntervalStartTimestamp( + address avs, + uint256 startTimestamp, + uint256 duration + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, avs); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp - duration - 1 + ); + cheats.assume(startTimestamp % CALCULATION_INTERVAL_SECONDS != 0); + + // 2. Create operator directed rewards submission input param + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new OperatorDirectedRewardsSubmission[](1); + performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected revert + cheats.prank(avs); + cheats.expectRevert(InvalidStartTimestampRemainder.selector); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + } + + // Revert with retroactive rewards enabled and set too far in past + // - either before genesis rewards timestamp + // - before max retroactive length + function testFuzz_Revert_WhenRewardsSubmissionTooStale( + address avs, + uint256 startTimestamp, + uint256 duration + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, avs); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound(startTimestamp, 0, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH - 1); + startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new OperatorDirectedRewardsSubmission[](1); + performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected revert + cheats.prank(avs); + cheats.expectRevert(StartTimestampTooFarInPast.selector); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + } + + // Revert when not retroactive + function testFuzz_Revert_WhenRewardsSubmissionNotRetroactive( + address avs, + uint256 startTimestamp, + uint256 duration + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, avs); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp, block.timestamp - duration + CALCULATION_INTERVAL_SECONDS, type(uint32).max - duration + ); + startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new OperatorDirectedRewardsSubmission[](1); + performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected revert + cheats.prank(avs); + cheats.expectRevert(SubmissionNotRetroactive.selector); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + } + + // Revert with non whitelisted strategy + function testFuzz_Revert_WhenInvalidStrategy( + address avs, + uint256 startTimestamp, + uint256 duration + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, avs); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp - duration - 1 + ); + startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new OperatorDirectedRewardsSubmission[](1); + defaultStrategyAndMultipliers[0].strategy = IStrategy(address(999)); + performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected revert + cheats.prank(avs); + cheats.expectRevert(StrategyNotWhitelisted.selector); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + } + + // Revert when duplicate strategies + function testFuzz_Revert_WhenDuplicateStrategies( + address avs, + uint256 startTimestamp, + uint256 duration + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, avs); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp - duration - 1 + ); + startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new OperatorDirectedRewardsSubmission[](1); + StrategyAndMultiplier[] memory dupStratsAndMultipliers = new StrategyAndMultiplier[](2); + dupStratsAndMultipliers[0] = defaultStrategyAndMultipliers[0]; + dupStratsAndMultipliers[1] = defaultStrategyAndMultipliers[0]; + performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: dupStratsAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected revert + cheats.prank(avs); + cheats.expectRevert(StrategiesNotInAscendingOrder.selector); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + } + + /** + * @notice test a single rewards submission asserting for the following + * - correct event emitted + * - submission nonce incrementation by 1, and rewards submission hash being set in storage. + * - rewards submission hash being set in storage + * - token balance before and after of avs and rewardsCoordinator + */ + function testFuzz_createOperatorDirectedOperatorSetRewardsSubmission_SingleSubmission( + address avs, + uint256 startTimestamp, + uint256 duration + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, avs); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp - duration - 1 + ); + startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new OperatorDirectedRewardsSubmission[](1); + performanceRewardsSubmissions[0] = OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected event emitted + uint256 avsBalanceBefore = rewardToken.balanceOf(avs); + uint256 rewardsCoordinatorBalanceBefore = rewardToken.balanceOf(address(rewardsCoordinator)); + + cheats.startPrank(avs); + uint256 amount = _getTotalRewardsAmount(defaultOperatorRewards); + rewardToken.approve(address(rewardsCoordinator), amount); + uint256 currSubmissionNonce = rewardsCoordinator.submissionNonce(avs); + bytes32 rewardsSubmissionHash = + keccak256(abi.encode(avs, currSubmissionNonce, performanceRewardsSubmissions[0])); + cheats.expectEmit(true, true, true, true, address(rewardsCoordinator)); + emit OperatorDirectedOperatorSetRewardsSubmissionCreated( + avs, operatorSet, rewardsSubmissionHash, currSubmissionNonce, performanceRewardsSubmissions[0] + ); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + cheats.stopPrank(); + + assertTrue( + rewardsCoordinator.isOperatorSetPerformanceRewardsSubmissionHash(avs, rewardsSubmissionHash), + "rewards submission hash not submitted" + ); + assertEq(currSubmissionNonce + 1, rewardsCoordinator.submissionNonce(avs), "submission nonce not incremented"); + assertEq( + avsBalanceBefore - amount, + rewardToken.balanceOf(avs), + "AVS balance not decremented by amount of rewards submission" + ); + assertEq( + rewardsCoordinatorBalanceBefore + amount, + rewardToken.balanceOf(address(rewardsCoordinator)), + "RewardsCoordinator balance not incremented by amount of rewards submission" + ); + } + + /** + * @notice Same test as above, uses UAM + * - correct event emitted + * - submission nonce incrementation by 1, and rewards submission hash being set in storage. + * - rewards submission hash being set in storage + * - token balance before and after of avs and rewardsCoordinator + */ + function testFuzz_createOperatorDirectedOperatorSetRewardsSubmission_SingleSubmission_UAM( + address avs, + uint256 startTimestamp, + uint256 duration + ) public filterFuzzedAddressInputs(avs) { + cheats.assume(avs != address(0)); + + operatorSet = OperatorSet(avs, 1); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + // Set UAM + cheats.prank(avs); + permissionController.setAppointee( + avs, + defaultAppointee, + address(rewardsCoordinator), + IRewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission.selector + ); + + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = + new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, defaultAppointee); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp - duration - 1 + ); + startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[] memory performanceRewardsSubmissions = + new IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[](1); + performanceRewardsSubmissions[0] = IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. call createOperatorDirectedOperatorSetRewardsSubmission() with expected event emitted + uint256 submitterBalanceBefore = rewardToken.balanceOf(defaultAppointee); + uint256 rewardsCoordinatorBalanceBefore = rewardToken.balanceOf(address(rewardsCoordinator)); + + cheats.startPrank(defaultAppointee); + uint256 amount = _getTotalRewardsAmount(defaultOperatorRewards); + rewardToken.approve(address(rewardsCoordinator), amount); + uint256 currSubmissionNonce = rewardsCoordinator.submissionNonce(avs); + bytes32 rewardsSubmissionHash = + keccak256(abi.encode(avs, currSubmissionNonce, performanceRewardsSubmissions[0])); + cheats.expectEmit(true, true, true, true, address(rewardsCoordinator)); + emit OperatorDirectedOperatorSetRewardsSubmissionCreated( + defaultAppointee, operatorSet, rewardsSubmissionHash, currSubmissionNonce, performanceRewardsSubmissions[0] + ); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, performanceRewardsSubmissions); + cheats.stopPrank(); + + assertTrue( + rewardsCoordinator.isOperatorSetPerformanceRewardsSubmissionHash(avs, rewardsSubmissionHash), + "rewards submission hash not submitted" + ); + assertEq(currSubmissionNonce + 1, rewardsCoordinator.submissionNonce(avs), "submission nonce not incremented"); + assertEq( + submitterBalanceBefore - amount, + rewardToken.balanceOf(defaultAppointee), + "Submitter balance not decremented by amount of rewards submission" + ); + assertEq( + rewardsCoordinatorBalanceBefore + amount, + rewardToken.balanceOf(address(rewardsCoordinator)), + "RewardsCoordinator balance not incremented by amount of rewards submission" + ); + } + + /** + * @notice test a multiple rewards submission asserting for the following + * - correct event emitted + * - submission nonce incrementation by 1, and rewards submission hash being set in storage. + * - rewards submission hash being set in storage + * - token balance before and after of avs and rewardsCoordinator + */ + function testFuzz_createOperatorDirectedOperatorSetRewardsSubmission_MultipleSubmissions( + FuzzOperatorDirectedAVSRewardsSubmission memory param, + uint256 numSubmissions + ) public filterFuzzedAddressInputs(param.avs) { + cheats.assume(2 <= numSubmissions && numSubmissions <= 10); + cheats.assume(param.avs != address(0)); + + operatorSet = OperatorSet(param.avs, 2); + allocationManagerMock.setIsOperatorSet(operatorSet, true); + + cheats.prank(rewardsCoordinator.owner()); + + OperatorDirectedRewardsSubmission[] memory rewardsSubmissions = + new OperatorDirectedRewardsSubmission[](numSubmissions); + bytes32[] memory rewardsSubmissionHashes = new bytes32[](numSubmissions); + uint256 startSubmissionNonce = rewardsCoordinator.submissionNonce(param.avs); + _deployMockRewardTokens(param.avs, numSubmissions); + + uint256[] memory avsBalancesBefore = _getBalanceForTokens(rewardTokens, param.avs); + uint256[] memory rewardsCoordinatorBalancesBefore = + _getBalanceForTokens(rewardTokens, address(rewardsCoordinator)); + uint256[] memory amounts = new uint256[](numSubmissions); + + // Create multiple rewards submissions and their expected event + for (uint256 i = 0; i < numSubmissions; ++i) { + // 1. Bound fuzz inputs to valid ranges and amounts using randSeed for each + amounts[i] = _getTotalRewardsAmount(defaultOperatorRewards); + param.duration = bound(param.duration, 0, MAX_REWARDS_DURATION); + param.duration = param.duration - (param.duration % CALCULATION_INTERVAL_SECONDS); + param.startTimestamp = bound( + param.startTimestamp + i, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp + uint256(MAX_FUTURE_LENGTH) + ); + param.startTimestamp = param.startTimestamp - (param.startTimestamp % CALCULATION_INTERVAL_SECONDS); + + param.duration = bound(param.duration, 0, MAX_REWARDS_DURATION); + param.duration = param.duration - (param.duration % CALCULATION_INTERVAL_SECONDS); + param.startTimestamp = bound( + param.startTimestamp, + uint256(_maxTimestamp(GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH)) + + CALCULATION_INTERVAL_SECONDS - 1, + block.timestamp - param.duration - 1 + ); + param.startTimestamp = param.startTimestamp - (param.startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create rewards submission input param + OperatorDirectedRewardsSubmission memory rewardsSubmission = IRewardsCoordinatorTypes + .OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardTokens[i], + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(param.startTimestamp), + duration: uint32(param.duration), + description: "" + }); + rewardsSubmissions[i] = rewardsSubmission; + + // 3. expected event emitted for this rewardsSubmission + rewardsSubmissionHashes[i] = + keccak256(abi.encode(param.avs, startSubmissionNonce + i, rewardsSubmissions[i])); + cheats.expectEmit(true, true, true, true, address(rewardsCoordinator)); + emit OperatorDirectedOperatorSetRewardsSubmissionCreated( + param.avs, operatorSet, rewardsSubmissionHashes[i], startSubmissionNonce + i, rewardsSubmissions[i] + ); + } + + // 4. call createAVSRewardsSubmission() + cheats.prank(param.avs); + rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, rewardsSubmissions); + + // 5. Check for submissionNonce() and rewardsSubmissionHashes being set + assertEq( + startSubmissionNonce + numSubmissions, + rewardsCoordinator.submissionNonce(param.avs), + "submission nonce not incremented properly" + ); + + for (uint256 i = 0; i < numSubmissions; ++i) { + assertTrue( + rewardsCoordinator.isOperatorSetPerformanceRewardsSubmissionHash(param.avs, rewardsSubmissionHashes[i]), + "rewards submission hash not submitted" + ); + assertEq( + avsBalancesBefore[i] - amounts[i], + rewardTokens[i].balanceOf(param.avs), + "AVS balance not decremented by amount of rewards submission" + ); + assertEq( + rewardsCoordinatorBalancesBefore[i] + amounts[i], + rewardTokens[i].balanceOf(address(rewardsCoordinator)), + "RewardsCoordinator balance not incremented by amount of rewards submission" + ); + } + } +} + contract RewardsCoordinatorUnitTests_submitRoot is RewardsCoordinatorUnitTests { // only callable by rewardsUpdater function testFuzz_Revert_WhenNotRewardsUpdater(