diff --git a/lib/forge-std b/lib/forge-std index 4f57c59f0..fc560fa34 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 4f57c59f066a03d13de8c65bb34fca8247f5fcb2 +Subproject commit fc560fa34fa12a335a50c35d92e55a6628ca467c diff --git a/script/deploy/holesky/upgrade_preprod_rewardsCoordinator.s.sol b/script/deploy/holesky/upgrade_preprod_rewardsCoordinator.s.sol index 90e12a73f..0a4bb02a7 100644 --- a/script/deploy/holesky/upgrade_preprod_rewardsCoordinator.s.sol +++ b/script/deploy/holesky/upgrade_preprod_rewardsCoordinator.s.sol @@ -8,7 +8,7 @@ import "./Deploy_Test_RewardsCoordinator.s.sol"; * anvil --fork-url $RPC_HOLESKY * * Holesky testnet: Deploy/Upgrade RewardsCoordinator - * forge script script/deploy/holesky/upgrade_preprod_rewardsCoordinator.s.sol --rpc-url $RPC_HOLESKY --private-key $PRIVATE_KEY --broadcast -vvvv + * forge script script/deploy/holesky/upgrade_preprod_rewardsCoordinator.s.sol --rpc-url $RPC_HOLESKY --private-key $PRIVATE_KEY --broadcast -vvvv * */ contract Upgrade_Preprod_RewardsCoordinator is Deploy_Test_RewardsCoordinator { @@ -39,6 +39,5 @@ contract Upgrade_Preprod_RewardsCoordinator is Deploy_Test_RewardsCoordinator { _verifyImplementations(); _verifyContractsInitialized(false); _verifyInitializationParams(); - } } diff --git a/src/contracts/core/RewardsCoordinator.sol b/src/contracts/core/RewardsCoordinator.sol index 1bc73c99b..a601b7f21 100644 --- a/src/contracts/core/RewardsCoordinator.sol +++ b/src/contracts/core/RewardsCoordinator.sol @@ -45,6 +45,8 @@ contract RewardsCoordinator is uint8 internal constant PAUSED_SUBMIT_DISABLE_ROOTS = 3; /// @dev Index for flag that pauses calling rewardAllStakersAndOperators uint8 internal constant PAUSED_REWARD_ALL_STAKERS_AND_OPERATORS = 4; + /// @dev Index for flag that pauses calling createAVSPerformanceRewardsSubmission + uint8 internal constant PAUSED_AVS_PERFORMANCE_REWARDS_SUBMISSION = 5; /// @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; @@ -118,11 +120,9 @@ contract RewardsCoordinator is */ /// @inheritdoc IRewardsCoordinator - function createAVSRewardsSubmission(RewardsSubmission[] calldata rewardsSubmissions) - external - onlyWhenNotPaused(PAUSED_AVS_REWARDS_SUBMISSION) - nonReentrant - { + function createAVSRewardsSubmission( + RewardsSubmission[] calldata rewardsSubmissions + ) external onlyWhenNotPaused(PAUSED_AVS_REWARDS_SUBMISSION) nonReentrant { for (uint256 i = 0; i < rewardsSubmissions.length; i++) { RewardsSubmission calldata rewardsSubmission = rewardsSubmissions[i]; uint256 nonce = submissionNonce[msg.sender]; @@ -139,12 +139,9 @@ contract RewardsCoordinator is } /// @inheritdoc IRewardsCoordinator - function createRewardsForAllSubmission(RewardsSubmission[] calldata rewardsSubmissions) - external - onlyWhenNotPaused(PAUSED_REWARDS_FOR_ALL_SUBMISSION) - onlyRewardsForAllSubmitter - nonReentrant - { + function createRewardsForAllSubmission( + RewardsSubmission[] calldata rewardsSubmissions + ) external onlyWhenNotPaused(PAUSED_REWARDS_FOR_ALL_SUBMISSION) onlyRewardsForAllSubmitter nonReentrant { for (uint256 i = 0; i < rewardsSubmissions.length; i++) { RewardsSubmission calldata rewardsSubmission = rewardsSubmissions[i]; uint256 nonce = submissionNonce[msg.sender]; @@ -161,12 +158,9 @@ contract RewardsCoordinator is } /// @inheritdoc IRewardsCoordinator - function createRewardsForAllEarners(RewardsSubmission[] calldata rewardsSubmissions) - external - onlyWhenNotPaused(PAUSED_REWARD_ALL_STAKERS_AND_OPERATORS) - onlyRewardsForAllSubmitter - nonReentrant - { + function createRewardsForAllEarners( + RewardsSubmission[] calldata rewardsSubmissions + ) external onlyWhenNotPaused(PAUSED_REWARD_ALL_STAKERS_AND_OPERATORS) onlyRewardsForAllSubmitter nonReentrant { for (uint256 i = 0; i < rewardsSubmissions.length; i++) { RewardsSubmission calldata rewardsSubmission = rewardsSubmissions[i]; uint256 nonce = submissionNonce[msg.sender]; @@ -178,41 +172,59 @@ contract RewardsCoordinator is submissionNonce[msg.sender] = nonce + 1; emit RewardsSubmissionForAllEarnersCreated( - msg.sender, nonce, rewardsSubmissionForAllEarnersHash, rewardsSubmission + msg.sender, + nonce, + rewardsSubmissionForAllEarnersHash, + rewardsSubmission ); rewardsSubmission.token.safeTransferFrom(msg.sender, address(this), rewardsSubmission.amount); } } + /// @inheritdoc IRewardsCoordinator + function createAVSPerformanceRewardsSubmission( + address avs, + PerformanceRewardsSubmission[] calldata performanceRewardsSubmissions + ) external onlyWhenNotPaused(PAUSED_AVS_PERFORMANCE_REWARDS_SUBMISSION) nonReentrant { + require(msg.sender == avs, "RewardsCoordinator.createAVSPerformanceRewardsSubmission: caller is not the AVS"); + + for (uint256 i = 0; i < performanceRewardsSubmissions.length; i++) { + PerformanceRewardsSubmission calldata performanceRewardsSubmission = performanceRewardsSubmissions[i]; + uint256 nonce = submissionNonce[msg.sender]; + bytes32 performanceRewardsSubmissionHash = keccak256( + abi.encode(msg.sender, nonce, performanceRewardsSubmission) + ); + + uint256 totalAmount = _validatePerformanceRewardsSubmission(performanceRewardsSubmission); + + isAVSPerformanceRewardsSubmissionHash[msg.sender][performanceRewardsSubmissionHash] = true; + submissionNonce[msg.sender] = nonce + 1; + + emit AVSPerformanceRewardsSubmissionCreated( + msg.sender, + nonce, + performanceRewardsSubmissionHash, + performanceRewardsSubmission + ); + performanceRewardsSubmission.token.safeTransferFrom(msg.sender, address(this), totalAmount); + } + } + /// @inheritdoc IRewardsCoordinator function processClaim( RewardsMerkleClaim calldata claim, address recipient ) external onlyWhenNotPaused(PAUSED_PROCESS_CLAIM) nonReentrant { - DistributionRoot memory root = _distributionRoots[claim.rootIndex]; - _checkClaim(claim, root); - // If claimerFor earner is not set, claimer is by default the earner. Else set to claimerFor - address earner = claim.earnerLeaf.earner; - address claimer = claimerFor[earner]; - if (claimer == address(0)) { - claimer = earner; - } - require(msg.sender == claimer, "RewardsCoordinator.processClaim: caller is not valid claimer"); - for (uint256 i = 0; i < claim.tokenIndices.length; i++) { - TokenTreeMerkleLeaf calldata tokenLeaf = claim.tokenLeaves[i]; - - uint256 currCumulativeClaimed = cumulativeClaimed[earner][tokenLeaf.token]; - require( - tokenLeaf.cumulativeEarnings > currCumulativeClaimed, - "RewardsCoordinator.processClaim: cumulativeEarnings must be gt than cumulativeClaimed" - ); - - // Calculate amount to claim and update cumulativeClaimed - uint256 claimAmount = tokenLeaf.cumulativeEarnings - currCumulativeClaimed; - cumulativeClaimed[earner][tokenLeaf.token] = tokenLeaf.cumulativeEarnings; + _processClaim(claim, recipient); + } - tokenLeaf.token.safeTransfer(recipient, claimAmount); - emit RewardsClaimed(root.root, earner, claimer, recipient, tokenLeaf.token, claimAmount); + /// @inheritdoc IRewardsCoordinator + function processClaims( + RewardsMerkleClaim[] calldata claims, + address recipient + ) external onlyWhenNotPaused(PAUSED_PROCESS_CLAIM) nonReentrant { + for (uint256 i = 0; i < claims.length; i++) { + _processClaim(claims[i], recipient); } } @@ -271,6 +283,19 @@ contract RewardsCoordinator is _setGlobalOperatorCommission(_globalCommissionBips); } + /// @inheritdoc IRewardsCoordinator + function setOperatorAVSCommission(address operator, address avs, uint16 commission) external { + require(msg.sender == operator, "RewardsCoordinator.setOperatorAVSCommission: caller is not the operator"); + //TODO: Add a check to ensure that the operator is registered to the AVS. + //TODO: Add a 7 day delay. + + OperatorAVSCommission storage operatorAVSCommission = operatorAVSCommissionBips[operator][avs]; + + emit OperatorAVSCommissionBipsSet(operator, avs, operatorAVSCommission.commissionBips, commission); + operatorAVSCommission.initialized = true; + operatorAVSCommission.commissionBips = commission; + } + /// @inheritdoc IRewardsCoordinator function setRewardsUpdater(address _rewardsUpdater) external onlyOwner { _setRewardsUpdater(_rewardsUpdater); @@ -315,8 +340,8 @@ contract RewardsCoordinator is "RewardsCoordinator._validateRewardsSubmission: startTimestamp must be a multiple of CALCULATION_INTERVAL_SECONDS" ); require( - block.timestamp - MAX_RETROACTIVE_LENGTH <= rewardsSubmission.startTimestamp - && GENESIS_REWARDS_TIMESTAMP <= rewardsSubmission.startTimestamp, + block.timestamp - MAX_RETROACTIVE_LENGTH <= rewardsSubmission.startTimestamp && + GENESIS_REWARDS_TIMESTAMP <= rewardsSubmission.startTimestamp, "RewardsCoordinator._validateRewardsSubmission: startTimestamp too far in the past" ); require( @@ -340,6 +365,85 @@ contract RewardsCoordinator is } } + /** + * @notice Validate a PerformanceRewardsSubmission. Called from `createAVSPerformanceRewardsSubmission`. + * @dev Not checking for `MAX_FUTURE_LENGTH` (Since Performance based reward submissions are retroactive) + * or `MAX_REWARDS_AMOUNT` (Since we no longer have the `1e38 - 1` limitation in the offchain rewards calculation) + * @param performanceRewardsSubmission PerformanceRewardsSubmission to validate. + * @return total amount to be transferred from the avs to the contract. + */ + function _validatePerformanceRewardsSubmission( + PerformanceRewardsSubmission calldata performanceRewardsSubmission + ) internal view returns (uint256) { + require( + performanceRewardsSubmission.strategiesAndMultipliers.length > 0, + "RewardsCoordinator._validatePerformanceRewardsSubmission: no strategies set" + ); + require( + performanceRewardsSubmission.operatorRewards.length > 0, + "RewardsCoordinator._validatePerformanceRewardsSubmission: no operators rewarded" + ); + + uint256 totalAmount = 0; + address currOperatorAddress = address(0); + for (uint256 i = 0; i < performanceRewardsSubmission.operatorRewards.length; ++i) { + OperatorReward calldata operatorReward = performanceRewardsSubmission.operatorRewards[i]; + require( + operatorReward.operator != address(0), + "RewardsCoordinator._validatePerformanceRewardsSubmission: operator cannot be 0 address" + ); + require( + currOperatorAddress < operatorReward.operator, + "RewardsCoordinator._validatePerformanceRewardsSubmission: operators must be in ascending order to handle duplicates" + ); + currOperatorAddress = operatorReward.operator; + require( + operatorReward.amount > 0, + "RewardsCoordinator._validatePerformanceRewardsSubmission: operator reward amount cannot be 0" + ); + totalAmount += operatorReward.amount; + } + + require( + performanceRewardsSubmission.duration <= MAX_REWARDS_DURATION, + "RewardsCoordinator._validatePerformanceRewardsSubmission: duration exceeds MAX_REWARDS_DURATION" + ); + require( + performanceRewardsSubmission.duration % CALCULATION_INTERVAL_SECONDS == 0, + "RewardsCoordinator._validatePerformanceRewardsSubmission: duration must be a multiple of CALCULATION_INTERVAL_SECONDS" + ); + require( + performanceRewardsSubmission.startTimestamp % CALCULATION_INTERVAL_SECONDS == 0, + "RewardsCoordinator._validatePerformanceRewardsSubmission: startTimestamp must be a multiple of CALCULATION_INTERVAL_SECONDS" + ); + require( + block.timestamp - MAX_RETROACTIVE_LENGTH <= performanceRewardsSubmission.startTimestamp && + GENESIS_REWARDS_TIMESTAMP <= performanceRewardsSubmission.startTimestamp, + "RewardsCoordinator._validatePerformanceRewardsSubmission: startTimestamp too far in the past" + ); + require( + performanceRewardsSubmission.startTimestamp + performanceRewardsSubmission.duration < block.timestamp, + "RewardsCoordinator._validatePerformanceRewardsSubmission: performance rewards submission is not retroactive" + ); + + // Require performanceRewardsSubmission is for whitelisted strategy or beaconChainETHStrategy + address currAddress = address(0); + for (uint256 i = 0; i < performanceRewardsSubmission.strategiesAndMultipliers.length; ++i) { + IStrategy strategy = performanceRewardsSubmission.strategiesAndMultipliers[i].strategy; + require( + strategyManager.strategyIsWhitelistedForDeposit(strategy) || strategy == beaconChainETHStrategy, + "RewardsCoordinator._validatePerformanceRewardsSubmission: invalid strategy considered" + ); + require( + currAddress < address(strategy), + "RewardsCoordinator._validatePerformanceRewardsSubmission: strategies must be in ascending order to handle duplicates" + ); + currAddress = address(strategy); + } + + return totalAmount; + } + function _checkClaim(RewardsMerkleClaim calldata claim, DistributionRoot memory root) internal view { require(!root.disabled, "RewardsCoordinator._checkClaim: root is disabled"); require(block.timestamp >= root.activatedAt, "RewardsCoordinator._checkClaim: root not activated yet"); @@ -430,15 +534,48 @@ contract RewardsCoordinator is // forgefmt: disable-next-item require( Merkle.verifyInclusionKeccak({ - root: root, - index: earnerLeafIndex, - proof: earnerProof, + root: root, + index: earnerLeafIndex, + proof: earnerProof, leaf: earnerLeafHash }), "RewardsCoordinator._verifyEarnerClaimProof: invalid earner claim proof" ); } + /** + * @notice Internal helper to process reward claims. + * @param claim The RewardsMerkleClaims to be processed. + * @param recipient The address recipient that receives the ERC20 rewards + */ + function _processClaim(RewardsMerkleClaim calldata claim, address recipient) internal { + DistributionRoot memory root = _distributionRoots[claim.rootIndex]; + _checkClaim(claim, root); + // If claimerFor earner is not set, claimer is by default the earner. Else set to claimerFor + address earner = claim.earnerLeaf.earner; + address claimer = claimerFor[earner]; + if (claimer == address(0)) { + claimer = earner; + } + require(msg.sender == claimer, "RewardsCoordinator.processClaim: caller is not valid claimer"); + for (uint256 i = 0; i < claim.tokenIndices.length; i++) { + TokenTreeMerkleLeaf calldata tokenLeaf = claim.tokenLeaves[i]; + + uint256 currCumulativeClaimed = cumulativeClaimed[earner][tokenLeaf.token]; + require( + tokenLeaf.cumulativeEarnings > currCumulativeClaimed, + "RewardsCoordinator.processClaim: cumulativeEarnings must be gt than cumulativeClaimed" + ); + + // Calculate amount to claim and update cumulativeClaimed + uint256 claimAmount = tokenLeaf.cumulativeEarnings - currCumulativeClaimed; + cumulativeClaimed[earner][tokenLeaf.token] = tokenLeaf.cumulativeEarnings; + + tokenLeaf.token.safeTransfer(recipient, claimAmount); + emit RewardsClaimed(root.root, earner, claimer, recipient, tokenLeaf.token, claimAmount); + } + } + function _setActivationDelay(uint32 _activationDelay) internal { emit ActivationDelaySet(activationDelay, _activationDelay); activationDelay = _activationDelay; diff --git a/src/contracts/core/RewardsCoordinatorStorage.sol b/src/contracts/core/RewardsCoordinatorStorage.sol index ca30e0f18..e4d3cdbeb 100644 --- a/src/contracts/core/RewardsCoordinatorStorage.sol +++ b/src/contracts/core/RewardsCoordinatorStorage.sol @@ -57,7 +57,7 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator { */ DistributionRoot[] internal _distributionRoots; - /// Slot 3 + /// Slot 2 /// @notice The address of the entity that can update the contract with new merkle roots address public rewardsUpdater; /// @notice Delay in timestamp (seconds) before a posted root can be claimed against @@ -89,6 +89,12 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator { /// if rewards submission hash for all stakers and operators has been submitted mapping(address => mapping(bytes32 => bool)) public isRewardsSubmissionForAllEarnersHash; + /// @notice Mapping: avs => avsPerformanceRewardsSubmissionHash => bool to check if performance rewards submission hash has been submitted + mapping(address => mapping(bytes32 => bool)) public isAVSPerformanceRewardsSubmissionHash; + + /// @notice Mapping: operator => avs => OperatorAVSCommission. The commission an operator takes for a specific AVS. + mapping(address => mapping(address => OperatorAVSCommission)) public operatorAVSCommissionBips; + constructor( IDelegationManager _delegationManager, IStrategyManager _strategyManager, @@ -120,5 +126,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[40] private __gap; + uint256[38] private __gap; } diff --git a/src/contracts/interfaces/IRewardsCoordinator.sol b/src/contracts/interfaces/IRewardsCoordinator.sol index 933758618..e690ea7fa 100644 --- a/src/contracts/interfaces/IRewardsCoordinator.sol +++ b/src/contracts/interfaces/IRewardsCoordinator.sol @@ -26,6 +26,26 @@ interface IRewardsCoordinator { uint96 multiplier; } + /** + * @notice A reward struct for an operator + * @param operator The operator to be rewarded + * @param amount The reward amount for the operator + */ + struct OperatorReward { + address operator; + uint256 amount; + } + + /** + * @notice A commission struct for an Operator per AVS + * @param initialized Whether the commission per AVS has been initialized + * @param commissionBips The commission in basis points + */ + struct OperatorAVSCommission { + bool initialized; + uint16 commissionBips; + } + /** * Sliding Window for valid RewardsSubmission startTimestamp * @@ -60,6 +80,42 @@ interface IRewardsCoordinator { uint32 duration; } + /** + * Sliding Window for valid PerformanceRewardsSubmission startTimestamp + * + * Scenario A: GENESIS_REWARDS_TIMESTAMP IS WITHIN RANGE + * <--------MAX_RETROACTIVE_LENGTH--------> t (block.timestamp) + * <--valid range for startTimestamp-- + * ^ + * GENESIS_REWARDS_TIMESTAMP + * + * + * Scenario B: GENESIS_REWARDS_TIMESTAMP IS OUT OF RANGE + * <--------MAX_RETROACTIVE_LENGTH--------> t (block.timestamp) + * <----valid range for startTimestamp---- + * ^ + * GENESIS_REWARDS_TIMESTAMP + * + * @notice PerformanceRewardsSubmission struct submitted by AVSs when making performance-based rewards for their operators and stakers + * PerformanceRewardsSubmission can be for a time range within the valid window for startTimestamp and must be within max duration. + * See `createAVSPerformanceRewardsSubmission()` for more details. + * @param strategiesAndMultipliers The strategies and their relative weights + * cannot have duplicate strategies and need to be sorted in ascending address order + * @param token The rewards token to be distributed. + * @param operatorRewards The rewards for the operators. The sum of all operator rewards equals the total amount of tokens deposited in the submission. + * The operators cannot have duplicate addresses and need to be sorted in ascending address order. + * @param startTimestamp The timestamp (seconds) at which the submission range is considered for distribution + * It is a retroactive payment and has to be strictly less than `block.timestamp`. See the diagram above. + * @param duration The duration of the submission range in seconds. Must be <= MAX_REWARDS_DURATION. + */ + struct PerformanceRewardsSubmission { + StrategyAndMultiplier[] strategiesAndMultipliers; + IERC20 token; + OperatorReward[] operatorRewards; + uint32 startTimestamp; + uint32 duration; + } + /** * @notice A distribution root is a merkle root of the distribution of earnings for a given period. * The RewardsCoordinator stores all historical distribution roots so that earners can claim their earnings against older roots @@ -150,13 +206,28 @@ interface IRewardsCoordinator { bytes32 indexed rewardsSubmissionHash, RewardsSubmission rewardsSubmission ); + /// @notice emitted when an AVS creates a valid PerformanceRewardsSubmission + event AVSPerformanceRewardsSubmissionCreated( + address indexed avs, + uint256 indexed submissionNonce, + bytes32 indexed performanceRewardsSubmissionHash, + PerformanceRewardsSubmission performanceRewardsSubmission + ); /// @notice rewardsUpdater is responsible for submiting DistributionRoots, only owner can set rewardsUpdater event RewardsUpdaterSet(address indexed oldRewardsUpdater, address indexed newRewardsUpdater); event RewardsForAllSubmitterSet( - address indexed rewardsForAllSubmitter, bool indexed oldValue, bool indexed newValue + address indexed rewardsForAllSubmitter, + bool indexed oldValue, + bool indexed newValue ); event ActivationDelaySet(uint32 oldActivationDelay, uint32 newActivationDelay); event GlobalCommissionBipsSet(uint16 oldGlobalCommissionBips, uint16 newGlobalCommissionBips); + event OperatorAVSCommissionBipsSet( + address indexed operator, + address indexed avs, + uint16 oldOperatorAVSCommissionBips, + uint16 newOperatorAVSCommissionBips + ); 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 event DistributionRootSubmitted( @@ -284,6 +355,24 @@ interface IRewardsCoordinator { */ function createRewardsForAllEarners(RewardsSubmission[] calldata rewardsSubmissions) external; + /** + * @notice Creates a new performance-based rewards submission on behalf of an AVS, to be split amongst the operators and + * set of stakers delegated to operators who are registered to the `avs`. + * @param avs The AVS on behalf of which the reward is being submitted + * @param performanceRewardsSubmissions The performance rewards submissions being created + * @dev Expected to be called by the ServiceManager of the AVS on behalf of which the submission is being made + * @dev The duration of the `rewardsSubmission` cannot exceed `MAX_REWARDS_DURATION` + * @dev The tokens are sent to the `RewardsCoordinator` contract + * @dev The `RewardsCoordinator` contract needs a token approval of sum of all `operatorRewards` in the `performanceRewardsSubmissions`, before calling this function. + * @dev Strategies must be in ascending order of addresses to check for duplicates + * @dev Operators must be in ascending order of addresses to check for duplicates. + * @dev This function will revert if the `performanceRewardsSubmissions` is malformed. + */ + function createAVSPerformanceRewardsSubmission( + address avs, + PerformanceRewardsSubmission[] 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, @@ -298,6 +387,20 @@ interface IRewardsCoordinator { */ function processClaim(RewardsMerkleClaim calldata claim, address recipient) external; + /** + * @notice Batch 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, + * they can simply claim against the latest root and the contract will calculate the difference between + * their cumulativeEarnings and cumulativeClaimed. This difference is then transferred to recipient address. + * @param claims The RewardsMerkleClaims to be processed. + * Contains the root index, earner, token leaves, and required proofs + * @param recipient The address recipient that receives the ERC20 rewards + * @dev only callable by the valid claimer, that is + * if claimerFor[claim.earner] is address(0) then only the earner can claim, otherwise only + * claimerFor[claim.earner] can claim the rewards. + */ + function processClaims(RewardsMerkleClaim[] calldata claims, address recipient) external; + /** * @notice Creates a new distribution root. activatedAt is set to block.timestamp + activationDelay * @param root The merkle root of the distribution @@ -333,6 +436,15 @@ interface IRewardsCoordinator { */ function setGlobalOperatorCommission(uint16 _globalCommissionBips) external; + /** + * @notice Sets the commission for a specific operator for a specific avs + * @param operator The operator who is setting the commission + * @param avs The avs for which the commission is being set by the operator + * @param commission The commission for the operator for the specific avs + * @dev Only callable by the operator + */ + function setOperatorAVSCommission(address operator, address avs, uint16 commission) external; + /** * @notice Sets the permissioned `rewardsUpdater` address which can post new roots * @dev Only callable by the contract owner