diff --git a/contracts/src/interfaces/IRewardsRegistry.sol b/contracts/src/interfaces/IRewardsRegistry.sol index a3a53571..2cd06196 100644 --- a/contracts/src/interfaces/IRewardsRegistry.sol +++ b/contracts/src/interfaces/IRewardsRegistry.sol @@ -9,14 +9,18 @@ interface IRewardsRegistryErrors { error OnlyAVS(); /// @notice Thrown when a function is called by an address that is not the RewardsAgent. error OnlyRewardsAgent(); - /// @notice Thrown when rewards have already been claimed for the current merkle root. - error RewardsAlreadyClaimed(); /// @notice Thrown when a provided merkle proof is invalid. error InvalidMerkleProof(); /// @notice Thrown when rewards transfer fails. error RewardsTransferFailed(); /// @notice Thrown when the rewards merkle root is not set. error RewardsMerkleRootNotSet(); + /// @notice Thrown when trying to access a merkle root index that doesn't exist. + error InvalidMerkleRootIndex(); + /// @notice Thrown when trying to claim rewards for a root index that has already been claimed. + error RewardsAlreadyClaimedForIndex(); + /// @notice Thrown when the arrays provided to the batch claim function have mismatched lengths. + error ArrayLengthMismatch(); } /** @@ -27,16 +31,37 @@ interface IRewardsRegistryEvents { * @notice Emitted when a new merkle root is set * @param oldRoot The previous merkle root * @param newRoot The new merkle root + * @param newRootIndex The index of the new root in the history */ - event RewardsMerkleRootUpdated(bytes32 oldRoot, bytes32 newRoot); + event RewardsMerkleRootUpdated(bytes32 oldRoot, bytes32 newRoot, uint256 newRootIndex); /** - * @notice Emitted when rewards are claimed - * @param operatorAddress Address of the operator receiving rewards + * @notice Emitted when rewards are claimed for a specific root index + * @param operatorAddress Address of the operator that received the rewards + * @param rootIndex Index of the merkle root that the operator claimed rewards from * @param points Points earned by the operator * @param rewardsAmount Amount of rewards transferred */ - event RewardsClaimed(address indexed operatorAddress, uint256 points, uint256 rewardsAmount); + event RewardsClaimedForIndex( + address indexed operatorAddress, + uint256 indexed rootIndex, + uint256 points, + uint256 rewardsAmount + ); + + /** + * @notice Emitted when rewards are claimed for multiple root indices in a batch + * @param operatorAddress Address of the operator that received the rewards + * @param rootIndices Array of merkle root indices that the operator claimed rewards from + * @param points Array of points earned by the operator for each root index + * @param totalRewardsAmount Total amount of rewards transferred to the operator + */ + event RewardsBatchClaimedForIndices( + address indexed operatorAddress, + uint256[] rootIndices, + uint256[] points, + uint256 totalRewardsAmount + ); } /** @@ -54,18 +79,48 @@ interface IRewardsRegistry is IRewardsRegistryErrors, IRewardsRegistryEvents { ) external; /** - * @notice Claim rewards for an operator + * @notice Claim rewards for an operator from a specific merkle root index * @param operatorAddress Address of the operator to receive rewards + * @param rootIndex Index of the merkle root to claim from * @param operatorPoints Points earned by the operator * @param proof Merkle proof to validate the operator's rewards * @dev Only callable by the AVS (Service Manager) */ function claimRewards( address operatorAddress, + uint256 rootIndex, uint256 operatorPoints, bytes32[] calldata proof ) external; + /** + * @notice Claim rewards for an operator from the latest merkle root + * @param operatorAddress Address of the operator to receive rewards + * @param operatorPoints Points earned by the operator + * @param proof Merkle proof to validate the operator's rewards + * @dev Only callable by the AVS (Service Manager) + */ + function claimLatestRewards( + address operatorAddress, + uint256 operatorPoints, + bytes32[] calldata proof + ) external; + + /** + * @notice Claim rewards for an operator from multiple merkle root indices + * @param operatorAddress Address of the operator to receive rewards + * @param rootIndices Array of merkle root indices to claim from + * @param operatorPoints Array of points earned by the operator for each root + * @param proofs Array of merkle proofs to validate the operator's rewards + * @dev Only callable by the AVS (Service Manager) + */ + function claimRewardsBatch( + address operatorAddress, + uint256[] calldata rootIndices, + uint256[] calldata operatorPoints, + bytes32[][] calldata proofs + ) external; + /** * @notice Sets the rewards agent address in the RewardsRegistry contract * @param rewardsAgent New rewards agent address @@ -74,4 +129,42 @@ interface IRewardsRegistry is IRewardsRegistryErrors, IRewardsRegistryEvents { function setRewardsAgent( address rewardsAgent ) external; + + /** + * @notice Get the merkle root at a specific index + * @param index Index of the merkle root to retrieve + * @return The merkle root at the specified index + */ + function getMerkleRootByIndex( + uint256 index + ) external view returns (bytes32); + + /** + * @notice Get the latest merkle root index + * @return The index of the latest merkle root (returns 0 if no roots exist) + */ + function getLatestMerkleRootIndex() external view returns (uint256); + + /** + * @notice Get the latest merkle root + * @return The latest merkle root (returns bytes32(0) if no roots exist) + */ + function getLatestMerkleRoot() external view returns (bytes32); + + /** + * @notice Get the total number of merkle roots in history + * @return The total count of merkle roots + */ + function getMerkleRootHistoryLength() external view returns (uint256); + + /** + * @notice Check if an operator has claimed rewards for a specific root index + * @param operatorAddress Address of the operator + * @param rootIndex Index of the merkle root to check + * @return True if the operator has claimed rewards for this root index, false otherwise + */ + function hasClaimedByIndex( + address operatorAddress, + uint256 rootIndex + ) external view returns (bool); } diff --git a/contracts/src/interfaces/IServiceManager.sol b/contracts/src/interfaces/IServiceManager.sol index c5372616..fa62d1bf 100644 --- a/contracts/src/interfaces/IServiceManager.sol +++ b/contracts/src/interfaces/IServiceManager.sol @@ -134,17 +134,45 @@ interface IServiceManager is IServiceManagerUI, IServiceManagerErrors, IServiceM function setRewardsRegistry(uint32 operatorSetId, IRewardsRegistry rewardsRegistry) external; /** - * @notice Claim rewards for an operator from the specified operator set + * @notice Claim rewards for an operator from a specific merkle root index * @param operatorSetId The ID of the operator set + * @param rootIndex Index of the merkle root to claim from * @param operatorPoints Points earned by the operator * @param proof Merkle proof to validate the operator's rewards */ function claimOperatorRewards( uint32 operatorSetId, + uint256 rootIndex, uint256 operatorPoints, bytes32[] calldata proof ) external; + /** + * @notice Claim rewards for an operator from the latest merkle root + * @param operatorSetId The ID of the operator set + * @param operatorPoints Points earned by the operator + * @param proof Merkle proof to validate the operator's rewards + */ + function claimLatestOperatorRewards( + uint32 operatorSetId, + uint256 operatorPoints, + bytes32[] calldata proof + ) external; + + /** + * @notice Claim rewards for an operator from multiple merkle root indices + * @param operatorSetId The ID of the operator set + * @param rootIndices Array of merkle root indices to claim from + * @param operatorPoints Array of points earned by the operator for each root + * @param proofs Array of merkle proofs to validate the operator's rewards + */ + function claimOperatorRewardsBatch( + uint32 operatorSetId, + uint256[] calldata rootIndices, + uint256[] calldata operatorPoints, + bytes32[][] calldata proofs + ) external; + /** * @notice Sets the rewards agent address in the RewardsRegistry contract * @param rewardsAgent New rewards agent address diff --git a/contracts/src/middleware/RewardsRegistry.sol b/contracts/src/middleware/RewardsRegistry.sol index 202ca023..e062e5b8 100644 --- a/contracts/src/middleware/RewardsRegistry.sol +++ b/contracts/src/middleware/RewardsRegistry.sol @@ -44,9 +44,17 @@ contract RewardsRegistry is RewardsRegistryStorage { function updateRewardsMerkleRoot( bytes32 newMerkleRoot ) external override onlyRewardsAgent { - bytes32 oldRoot = lastRewardsMerkleRoot; - lastRewardsMerkleRoot = newMerkleRoot; - emit RewardsMerkleRootUpdated(oldRoot, newMerkleRoot); + // Get the old root (bytes32(0) if no roots exist) + bytes32 oldRoot = merkleRootHistory.length > 0 + ? merkleRootHistory[merkleRootHistory.length - 1] + : bytes32(0); + + // Add the new root to the history + uint256 newRootIndex = merkleRootHistory.length; + merkleRootHistory.push(newMerkleRoot); + + // Emit the corresponding event + emit RewardsMerkleRootUpdated(oldRoot, newMerkleRoot, newRootIndex); } /** @@ -61,47 +69,190 @@ contract RewardsRegistry is RewardsRegistryStorage { } /** - * @notice Claim rewards for an operator + * @notice Claim rewards for an operator from a specific merkle root index * @param operatorAddress Address of the operator to receive rewards + * @param rootIndex Index of the merkle root to claim from * @param operatorPoints Points earned by the operator * @param proof Merkle proof to validate the operator's rewards * @dev Only callable by the AVS (Service Manager) */ function claimRewards( address operatorAddress, + uint256 rootIndex, uint256 operatorPoints, bytes32[] calldata proof ) external override onlyAVS { - // Check that the lastRewardsMerkleRoot is not the default value - if (lastRewardsMerkleRoot == bytes32(0)) { + // Validate the claim and calculate rewards + uint256 rewardsAmount = _validateClaim(operatorAddress, rootIndex, operatorPoints, proof); + _transferRewards(operatorAddress, rewardsAmount); + + // Emit the corresponding event + emit RewardsClaimedForIndex(operatorAddress, rootIndex, operatorPoints, rewardsAmount); + } + + /** + * @notice Claim rewards for an operator from the latest merkle root + * @param operatorAddress Address of the operator to receive rewards + * @param operatorPoints Points earned by the operator + * @param proof Merkle proof to validate the operator's rewards + * @dev Only callable by the AVS (Service Manager) + */ + function claimLatestRewards( + address operatorAddress, + uint256 operatorPoints, + bytes32[] calldata proof + ) external override onlyAVS { + // Check that we have at least one merkle root + if (merkleRootHistory.length == 0) { revert RewardsMerkleRootNotSet(); } - // Check if operator has already claimed for this merkle root - if (operatorToLastClaimedRoot[operatorAddress] == lastRewardsMerkleRoot) { - revert RewardsAlreadyClaimed(); + // Claim from the latest root index + uint256 latestIndex = merkleRootHistory.length - 1; + uint256 rewardsAmount = _validateClaim(operatorAddress, latestIndex, operatorPoints, proof); + _transferRewards(operatorAddress, rewardsAmount); + + // Emit the corresponding event + emit RewardsClaimedForIndex(operatorAddress, latestIndex, operatorPoints, rewardsAmount); + } + + /** + * @notice Claim rewards for an operator from multiple merkle root indices + * @param operatorAddress Address of the operator to receive rewards + * @param rootIndices Array of merkle root indices to claim from + * @param operatorPoints Array of points earned by the operator for each root + * @param proofs Array of merkle proofs to validate the operator's rewards + * @dev Only callable by the AVS (Service Manager) + */ + function claimRewardsBatch( + address operatorAddress, + uint256[] calldata rootIndices, + uint256[] calldata operatorPoints, + bytes32[][] calldata proofs + ) external override onlyAVS { + // Check that the arrays have the same length + if (rootIndices.length != operatorPoints.length || rootIndices.length != proofs.length) { + revert ArrayLengthMismatch(); + } + + // Validate all claims and accumulate the total rewards + uint256 totalRewards = 0; + for (uint256 i = 0; i < rootIndices.length; i++) { + totalRewards += + _validateClaim(operatorAddress, rootIndices[i], operatorPoints[i], proofs[i]); + } + + // Transfer the total rewards in a single transaction + _transferRewards(operatorAddress, totalRewards); + + // Emit the corresponding event + emit RewardsBatchClaimedForIndices( + operatorAddress, rootIndices, operatorPoints, totalRewards + ); + } + + /** + * @notice Internal function to validate a claim and calculate rewards + * @param operatorAddress Address of the operator to receive rewards + * @param rootIndex Index of the merkle root to claim from + * @param operatorPoints Points earned by the operator + * @param proof Merkle proof to validate the operator's rewards + * @return rewardsAmount The amount of rewards calculated + */ + function _validateClaim( + address operatorAddress, + uint256 rootIndex, + uint256 operatorPoints, + bytes32[] calldata proof + ) internal returns (uint256 rewardsAmount) { + // Check that the root index to claim from exists + if (rootIndex >= merkleRootHistory.length) { + revert InvalidMerkleRootIndex(); + } + + // Check if operator has already claimed for this merkle root index + if (operatorClaimedByIndex[operatorAddress][rootIndex]) { + revert RewardsAlreadyClaimedForIndex(); } // Verify the merkle proof bytes32 leaf = keccak256(abi.encode(operatorAddress, operatorPoints)); - if (!MerkleProof.verify(proof, lastRewardsMerkleRoot, leaf)) { + if (!MerkleProof.verify(proof, merkleRootHistory[rootIndex], leaf)) { revert InvalidMerkleProof(); } // Calculate rewards - currently 1 point = 1 wei (placeholder) // TODO: Update the reward calculation formula with the proper relationship - uint256 rewardsAmount = operatorPoints; + rewardsAmount = operatorPoints; - // Update the operator's last claimed root - operatorToLastClaimedRoot[operatorAddress] = lastRewardsMerkleRoot; + // Mark as claimed for this specific index + operatorClaimedByIndex[operatorAddress][rootIndex] = true; + } + /** + * @notice Internal function to transfer rewards to an operator + * @param operatorAddress Address of the operator to receive rewards + * @param rewardsAmount Amount of rewards to transfer + */ + function _transferRewards(address operatorAddress, uint256 rewardsAmount) internal { // Transfer rewards to the operator (bool success,) = operatorAddress.call{value: rewardsAmount}(""); if (!success) { revert RewardsTransferFailed(); } + } - emit RewardsClaimed(operatorAddress, operatorPoints, rewardsAmount); + /** + * @notice Get the merkle root at a specific index + * @param index Index of the merkle root to retrieve + * @return The merkle root at the specified index + */ + function getMerkleRootByIndex( + uint256 index + ) external view override returns (bytes32) { + if (index >= merkleRootHistory.length) { + revert InvalidMerkleRootIndex(); + } + return merkleRootHistory[index]; + } + + /** + * @notice Get the latest merkle root index + * @return The index of the latest merkle root (returns 0 if no roots exist) + */ + function getLatestMerkleRootIndex() external view override returns (uint256) { + uint256 length = merkleRootHistory.length; + return length == 0 ? 0 : length - 1; + } + + /** + * @notice Get the latest merkle root + * @return The latest merkle root (returns bytes32(0) if no roots exist) + */ + function getLatestMerkleRoot() external view override returns (bytes32) { + uint256 length = merkleRootHistory.length; + return length == 0 ? bytes32(0) : merkleRootHistory[length - 1]; + } + + /** + * @notice Get the total number of merkle roots in history + * @return The total count of merkle roots + */ + function getMerkleRootHistoryLength() external view override returns (uint256) { + return merkleRootHistory.length; + } + + /** + * @notice Check if an operator has claimed rewards for a specific root index + * @param operatorAddress Address of the operator + * @param rootIndex Index of the merkle root to check + * @return True if the operator has claimed rewards for this root index + */ + function hasClaimedByIndex( + address operatorAddress, + uint256 rootIndex + ) external view override returns (bool) { + return operatorClaimedByIndex[operatorAddress][rootIndex]; } /** diff --git a/contracts/src/middleware/RewardsRegistryStorage.sol b/contracts/src/middleware/RewardsRegistryStorage.sol index b3e33535..5ac00b52 100644 --- a/contracts/src/middleware/RewardsRegistryStorage.sol +++ b/contracts/src/middleware/RewardsRegistryStorage.sol @@ -26,11 +26,11 @@ abstract contract RewardsRegistryStorage is IRewardsRegistry { /// @notice Address of the rewards agent contract address public rewardsAgent; - /// @notice Last rewards merkle root - bytes32 public lastRewardsMerkleRoot; + /// @notice History of all merkle roots, accessible by index + bytes32[] public merkleRootHistory; - /// @notice Mapping from operator ID to the last claimed merkle root - mapping(address => bytes32) public operatorToLastClaimedRoot; + /// @notice Mapping from operator to merkle root index to claimed status + mapping(address => mapping(uint256 => bool)) public operatorClaimedByIndex; /** * @notice Constructor to set up the immutable AVS address diff --git a/contracts/src/middleware/ServiceManagerBase.sol b/contracts/src/middleware/ServiceManagerBase.sol index d7a8e7cb..8ba0e2f5 100644 --- a/contracts/src/middleware/ServiceManagerBase.sol +++ b/contracts/src/middleware/ServiceManagerBase.sol @@ -303,12 +303,38 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage, IAVSRegistrar } /** - * @notice Claim rewards for an operator from the specified operator set + * @notice Claim rewards for an operator from a specific merkle root index * @param operatorSetId The ID of the operator set + * @param rootIndex Index of the merkle root to claim from * @param operatorPoints Points earned by the operator * @param proof Merkle proof to validate the operator's rewards */ function claimOperatorRewards( + uint32 operatorSetId, + uint256 rootIndex, + uint256 operatorPoints, + bytes32[] calldata proof + ) external virtual override { + // Get the rewards registry for this operator set + IRewardsRegistry rewardsRegistry = operatorSetToRewardsRegistry[operatorSetId]; + if (address(rewardsRegistry) == address(0)) { + revert NoRewardsRegistryForOperatorSet(); + } + + // Ensure the operator is part of the operator set + _ensureOperatorIsPartOfOperatorSet(msg.sender, operatorSetId); + + // Forward the claim to the rewards registry + rewardsRegistry.claimRewards(msg.sender, rootIndex, operatorPoints, proof); + } + + /** + * @notice Claim rewards for an operator from the latest merkle root + * @param operatorSetId The ID of the operator set + * @param operatorPoints Points earned by the operator + * @param proof Merkle proof to validate the operator's rewards + */ + function claimLatestOperatorRewards( uint32 operatorSetId, uint256 operatorPoints, bytes32[] calldata proof @@ -323,7 +349,33 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage, IAVSRegistrar _ensureOperatorIsPartOfOperatorSet(msg.sender, operatorSetId); // Forward the claim to the rewards registry - rewardsRegistry.claimRewards(msg.sender, operatorPoints, proof); + rewardsRegistry.claimLatestRewards(msg.sender, operatorPoints, proof); + } + + /** + * @notice Claim rewards for an operator from multiple merkle root indices + * @param operatorSetId The ID of the operator set + * @param rootIndices Array of merkle root indices to claim from + * @param operatorPoints Array of points earned by the operator for each root + * @param proofs Array of merkle proofs to validate the operator's rewards + */ + function claimOperatorRewardsBatch( + uint32 operatorSetId, + uint256[] calldata rootIndices, + uint256[] calldata operatorPoints, + bytes32[][] calldata proofs + ) external virtual override { + // Get the rewards registry for this operator set + IRewardsRegistry rewardsRegistry = operatorSetToRewardsRegistry[operatorSetId]; + if (address(rewardsRegistry) == address(0)) { + revert NoRewardsRegistryForOperatorSet(); + } + + // Ensure the operator is part of the operator set + _ensureOperatorIsPartOfOperatorSet(msg.sender, operatorSetId); + + // Forward the claim to the rewards registry + rewardsRegistry.claimRewardsBatch(msg.sender, rootIndices, operatorPoints, proofs); } /** diff --git a/contracts/test/RewardsRegistry.t.sol b/contracts/test/RewardsRegistry.t.sol index 744a6381..09d065fc 100644 --- a/contracts/test/RewardsRegistry.t.sol +++ b/contracts/test/RewardsRegistry.t.sol @@ -22,8 +22,19 @@ contract RewardsRegistryTest is AVSDeployer { bytes32[] public invalidProof; // Events - event RewardsMerkleRootUpdated(bytes32 oldRoot, bytes32 newRoot); - event RewardsClaimed(address indexed operatorAddress, uint256 points, uint256 rewardsAmount); + event RewardsMerkleRootUpdated(bytes32 oldRoot, bytes32 newRoot, uint256 newRootIndex); + event RewardsClaimedForIndex( + address indexed operatorAddress, + uint256 indexed rootIndex, + uint256 points, + uint256 rewardsAmount + ); + event RewardsBatchClaimedForIndices( + address indexed operatorAddress, + uint256[] rootIndices, + uint256[] points, + uint256 totalRewardsAmount + ); function setUp() public { _deployMockEigenLayerAndAVS(); @@ -97,13 +108,11 @@ contract RewardsRegistryTest is AVSDeployer { vm.prank(mockRewardsAgent); vm.expectEmit(true, true, true, true); - emit RewardsMerkleRootUpdated(bytes32(0), merkleRoot); + emit RewardsMerkleRootUpdated(bytes32(0), merkleRoot, 0); rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); - assertEq( - rewardsRegistry.lastRewardsMerkleRoot(), merkleRoot, "Merkle root should be updated" - ); + assertEq(rewardsRegistry.getLatestMerkleRoot(), merkleRoot, "Merkle root should be updated"); } function test_updateRewardsMerkleRoot_NotRewardsAgent() public { @@ -123,7 +132,7 @@ contract RewardsRegistryTest is AVSDeployer { vm.prank(mockRewardsAgent); vm.expectEmit(true, true, true, true); - emit RewardsMerkleRootUpdated(merkleRoot, newMerkleRoot); + emit RewardsMerkleRootUpdated(merkleRoot, newMerkleRoot, 1); rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot); } @@ -155,7 +164,7 @@ contract RewardsRegistryTest is AVSDeployer { * claimRewards Tests * * */ - function test_claimRewards() public { + function test_claimLatestRewards() public { // First update merkle root vm.prank(mockRewardsAgent); rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); @@ -168,15 +177,14 @@ contract RewardsRegistryTest is AVSDeployer { vm.prank(address(serviceManager)); vm.expectEmit(true, true, true, true); - emit RewardsClaimed(operatorAddress, operatorPoints, operatorPoints); + emit RewardsClaimedForIndex(operatorAddress, 0, operatorPoints, operatorPoints); - rewardsRegistry.claimRewards(operatorAddress, operatorPoints, validProof); + rewardsRegistry.claimLatestRewards(operatorAddress, operatorPoints, validProof); // Verify state changes - assertEq( - rewardsRegistry.operatorToLastClaimedRoot(operatorAddress), - merkleRoot, - "Operator's last claimed root should be updated" + assertTrue( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 0), + "Operator should have claimed from the latest root index" ); assertEq( operatorAddress.balance, @@ -185,7 +193,7 @@ contract RewardsRegistryTest is AVSDeployer { ); } - function test_claimRewards_NotAVS() public { + function test_claimLatestRewards_NotAVS() public { vm.prank(mockRewardsAgent); rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); @@ -193,10 +201,10 @@ contract RewardsRegistryTest is AVSDeployer { vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.OnlyAVS.selector)); - rewardsRegistry.claimRewards(operatorAddress, operatorPoints, validProof); + rewardsRegistry.claimLatestRewards(operatorAddress, operatorPoints, validProof); } - function test_claimRewards_AlreadyClaimed() public { + function test_claimLatestRewards_AlreadyClaimed() public { // First update merkle root vm.prank(mockRewardsAgent); rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); @@ -206,35 +214,35 @@ contract RewardsRegistryTest is AVSDeployer { // First claim succeeds vm.prank(address(serviceManager)); - rewardsRegistry.claimRewards(operatorAddress, operatorPoints, validProof); + rewardsRegistry.claimLatestRewards(operatorAddress, operatorPoints, validProof); // Second claim fails vm.prank(address(serviceManager)); vm.expectRevert( - abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimed.selector) + abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector) ); - rewardsRegistry.claimRewards(operatorAddress, operatorPoints, validProof); + rewardsRegistry.claimLatestRewards(operatorAddress, operatorPoints, validProof); } - function test_claimRewards_InvalidProof() public { + function test_claimLatestRewards_InvalidProof() public { vm.prank(mockRewardsAgent); rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); vm.prank(address(serviceManager)); vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.InvalidMerkleProof.selector)); - rewardsRegistry.claimRewards(operatorAddress, operatorPoints, invalidProof); + rewardsRegistry.claimLatestRewards(operatorAddress, operatorPoints, invalidProof); } - function test_claimRewards_NoMerkleRoot() public { - // lastRewardsMerkleRoot is not set + function test_claimLatestRewards_NoMerkleRoot() public { + // No merkle roots exist yet vm.prank(address(serviceManager)); vm.expectRevert( abi.encodeWithSelector(IRewardsRegistryErrors.RewardsMerkleRootNotSet.selector) ); - rewardsRegistry.claimRewards(operatorAddress, operatorPoints, validProof); + rewardsRegistry.claimLatestRewards(operatorAddress, operatorPoints, validProof); } - function test_claimRewards_DifferentRoot() public { + function test_claimLatestRewards_DifferentRoot() public { // First merkle root vm.prank(mockRewardsAgent); rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); @@ -244,7 +252,7 @@ contract RewardsRegistryTest is AVSDeployer { // First claim succeeds vm.prank(address(serviceManager)); - rewardsRegistry.claimRewards(operatorAddress, operatorPoints, validProof); + rewardsRegistry.claimLatestRewards(operatorAddress, operatorPoints, validProof); // Update to new merkle root vm.prank(mockRewardsAgent); @@ -257,16 +265,20 @@ contract RewardsRegistryTest is AVSDeployer { // Operator can claim again with new merkle root vm.prank(address(serviceManager)); - rewardsRegistry.claimRewards(operatorAddress, operatorPoints, newProof); + rewardsRegistry.claimLatestRewards(operatorAddress, operatorPoints, newProof); - assertEq( - rewardsRegistry.operatorToLastClaimedRoot(operatorAddress), - newMerkleRoot, - "Operator's last claimed root should be updated to new root" + // Verify both indices are now claimed + assertTrue( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 0), + "Operator should have claimed from first root index" + ); + assertTrue( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 1), + "Operator should have claimed from second root index" ); } - function test_claimRewards_InsufficientBalance() public { + function test_claimLatestRewards_InsufficientBalance() public { // Set merkle root vm.prank(mockRewardsAgent); rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); @@ -278,7 +290,7 @@ contract RewardsRegistryTest is AVSDeployer { vm.expectRevert( abi.encodeWithSelector(IRewardsRegistryErrors.RewardsTransferFailed.selector) ); - rewardsRegistry.claimRewards(operatorAddress, operatorPoints, validProof); + rewardsRegistry.claimLatestRewards(operatorAddress, operatorPoints, validProof); } function test_receive() public { @@ -290,4 +302,303 @@ contract RewardsRegistryTest is AVSDeployer { assertTrue(success, "Contract should be able to receive ETH"); assertEq(address(rewardsRegistry).balance, amount, "Contract balance should increase"); } + + /** + * + * Merkle Root History Tests * + * + */ + function test_getMerkleRootByIndex() public { + // Add first root + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); + + // Add second root + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot); + + // Test accessing by index + assertEq( + rewardsRegistry.getMerkleRootByIndex(0), + merkleRoot, + "First root should be accessible by index 0" + ); + assertEq( + rewardsRegistry.getMerkleRootByIndex(1), + newMerkleRoot, + "Second root should be accessible by index 1" + ); + } + + function test_getMerkleRootByIndex_InvalidIndex() public { + // Add one root + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); + + // Try to access invalid index + vm.expectRevert( + abi.encodeWithSelector(IRewardsRegistryErrors.InvalidMerkleRootIndex.selector) + ); + rewardsRegistry.getMerkleRootByIndex(1); + } + + function test_getLatestMerkleRootIndex() public { + // Initially should return 0 when no roots exist + assertEq( + rewardsRegistry.getLatestMerkleRootIndex(), 0, "Should return 0 when no roots exist" + ); + + // Add first root + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); + assertEq(rewardsRegistry.getLatestMerkleRootIndex(), 0, "Should return 0 for first root"); + + // Add second root + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot); + assertEq(rewardsRegistry.getLatestMerkleRootIndex(), 1, "Should return 1 for second root"); + } + + function test_getMerkleRootHistoryLength() public { + // Initially should be 0 + assertEq(rewardsRegistry.getMerkleRootHistoryLength(), 0, "Should be 0 initially"); + + // Add first root + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); + assertEq(rewardsRegistry.getMerkleRootHistoryLength(), 1, "Should be 1 after first root"); + + // Add second root + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot); + assertEq(rewardsRegistry.getMerkleRootHistoryLength(), 2, "Should be 2 after second root"); + } + + function test_historyPreservesQuickAccess() public { + // Add multiple roots + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); + + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot); + + // Latest root should be accessible directly without index + assertEq( + rewardsRegistry.getLatestMerkleRoot(), + newMerkleRoot, + "getLatestMerkleRoot should return latest root" + ); + + // But we should also be able to access by index + assertEq( + rewardsRegistry.getMerkleRootByIndex(1), + newMerkleRoot, + "Latest root should also be accessible by index" + ); + assertEq( + rewardsRegistry.getMerkleRootByIndex(0), + merkleRoot, + "Previous root should be accessible by index" + ); + } + + /** + * + * Index-based Claim Tests * + * + */ + function test_claimRewards() public { + // Add multiple roots + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); + + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot); + + // Add ETH to contract for rewards + vm.deal(address(rewardsRegistry), 1000 ether); + + uint256 initialBalance = operatorAddress.balance; + + // Claim from first root (index 0) + vm.prank(address(serviceManager)); + + vm.expectEmit(true, true, true, true); + emit RewardsClaimedForIndex(operatorAddress, 0, operatorPoints, operatorPoints); + + rewardsRegistry.claimRewards(operatorAddress, 0, operatorPoints, validProof); + + // Verify state changes + assertTrue( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 0), + "Operator should have claimed from index 0" + ); + assertEq( + operatorAddress.balance, + initialBalance + operatorPoints, + "Operator should receive correct rewards" + ); + } + + function test_claimRewards_InvalidIndex() public { + vm.deal(address(rewardsRegistry), 1000 ether); + + vm.prank(address(serviceManager)); + vm.expectRevert( + abi.encodeWithSelector(IRewardsRegistryErrors.InvalidMerkleRootIndex.selector) + ); + rewardsRegistry.claimRewards(operatorAddress, 0, operatorPoints, validProof); + } + + function test_claimRewards_AlreadyClaimed() public { + // Add root + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); + + vm.deal(address(rewardsRegistry), 1000 ether); + + // First claim succeeds + vm.prank(address(serviceManager)); + rewardsRegistry.claimRewards(operatorAddress, 0, operatorPoints, validProof); + + // Second claim fails + vm.prank(address(serviceManager)); + vm.expectRevert( + abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector) + ); + rewardsRegistry.claimRewards(operatorAddress, 0, operatorPoints, validProof); + } + + function test_hasClaimedByIndex() public { + // Add root + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); + + vm.deal(address(rewardsRegistry), 1000 ether); + + // Initially not claimed + assertFalse( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 0), + "Should not have claimed initially" + ); + + // Claim + vm.prank(address(serviceManager)); + rewardsRegistry.claimRewards(operatorAddress, 0, operatorPoints, validProof); + + // Now claimed + assertTrue( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 0), "Should have claimed after claim" + ); + } + + /** + * + * Batch Claim Tests * + * + */ + function test_claimRewardsBatch() public { + // Add multiple roots + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); + + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot); + + vm.deal(address(rewardsRegistry), 1000 ether); + + // Prepare batch claim data + uint256[] memory rootIndices = new uint256[](2); + rootIndices[0] = 0; + rootIndices[1] = 1; + + uint256[] memory points = new uint256[](2); + points[0] = operatorPoints; + points[1] = operatorPoints; + + bytes32[][] memory proofs = new bytes32[][](2); + proofs[0] = validProof; + + // Create proof for second root + bytes32[] memory newProof = new bytes32[](1); + bytes32 newSiblingLeaf = keccak256(abi.encodePacked("new sibling")); + newProof[0] = newSiblingLeaf; + proofs[1] = newProof; + + uint256 initialBalance = operatorAddress.balance; + + // Batch claim + vm.prank(address(serviceManager)); + + vm.expectEmit(true, true, true, true); + emit RewardsBatchClaimedForIndices(operatorAddress, rootIndices, points, operatorPoints * 2); + + rewardsRegistry.claimRewardsBatch(operatorAddress, rootIndices, points, proofs); + + // Verify both indices are claimed + assertTrue( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 0), + "Should have claimed from index 0" + ); + assertTrue( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 1), + "Should have claimed from index 1" + ); + + // Verify total rewards received + assertEq( + operatorAddress.balance, + initialBalance + (operatorPoints * 2), + "Should receive rewards from both claims" + ); + } + + function test_claimRewardsBatch_ArrayLengthMismatch() public { + uint256[] memory rootIndices = new uint256[](2); + uint256[] memory points = new uint256[](1); // Wrong length + bytes32[][] memory proofs = new bytes32[][](2); + + vm.prank(address(serviceManager)); + vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.ArrayLengthMismatch.selector)); + rewardsRegistry.claimRewardsBatch(operatorAddress, rootIndices, points, proofs); + } + + function test_claimRewardsBatch_PartialClaimFailure() public { + // Add roots + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); + + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot); + + vm.deal(address(rewardsRegistry), 1000 ether); + + // Claim from index 0 first + vm.prank(address(serviceManager)); + rewardsRegistry.claimRewards(operatorAddress, 0, operatorPoints, validProof); + + // Now try batch claim that includes already claimed index 0 + uint256[] memory rootIndices = new uint256[](2); + rootIndices[0] = 0; // Already claimed + rootIndices[1] = 1; + + uint256[] memory points = new uint256[](2); + points[0] = operatorPoints; + points[1] = operatorPoints; + + bytes32[][] memory proofs = new bytes32[][](2); + proofs[0] = validProof; + + bytes32[] memory newProof = new bytes32[](1); + bytes32 newSiblingLeaf = keccak256(abi.encodePacked("new sibling")); + newProof[0] = newSiblingLeaf; + proofs[1] = newProof; + + // Should fail because index 0 is already claimed + vm.prank(address(serviceManager)); + vm.expectRevert( + abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector) + ); + rewardsRegistry.claimRewardsBatch(operatorAddress, rootIndices, points, proofs); + } } diff --git a/contracts/test/ServiceManagerRewardsRegistry.t.sol b/contracts/test/ServiceManagerRewardsRegistry.t.sol index b3c154ac..bbd77929 100644 --- a/contracts/test/ServiceManagerRewardsRegistry.t.sol +++ b/contracts/test/ServiceManagerRewardsRegistry.t.sol @@ -22,12 +22,29 @@ contract ServiceManagerRewardsRegistryTest is AVSDeployer { // Test data uint32 public operatorSetId; bytes32 public merkleRoot; + bytes32 public secondMerkleRoot; + bytes32 public thirdMerkleRoot; uint256 public operatorPoints; + uint256 public secondOperatorPoints; + uint256 public thirdOperatorPoints; bytes32[] public validProof; + bytes32[] public secondValidProof; + bytes32[] public thirdValidProof; // Events event RewardsRegistrySet(uint32 indexed operatorSetId, address indexed rewardsRegistry); - event RewardsClaimed(address indexed operatorAddress, uint256 points, uint256 rewardsAmount); + event RewardsClaimedForIndex( + address indexed operatorAddress, + uint256 indexed rootIndex, + uint256 points, + uint256 rewardsAmount + ); + event RewardsBatchClaimedForIndices( + address indexed operatorAddress, + uint256[] rootIndices, + uint256[] points, + uint256 totalRewardsAmount + ); function setUp() public { _deployMockEigenLayerAndAVS(); @@ -39,28 +56,65 @@ contract ServiceManagerRewardsRegistryTest is AVSDeployer { // Configure test data operatorSetId = 1; operatorPoints = 100; + secondOperatorPoints = 200; + thirdOperatorPoints = 150; - // Create a merkle tree where we know what the root should be based on our leaf - bytes32 leaf = keccak256(abi.encode(operatorAddress, operatorPoints)); - bytes32 siblingLeaf = keccak256(abi.encodePacked("sibling")); - (bytes32 leftLeaf, bytes32 rightLeaf) = - leaf < siblingLeaf ? (leaf, siblingLeaf) : (siblingLeaf, leaf); - merkleRoot = keccak256(abi.encodePacked(leftLeaf, rightLeaf)); - validProof = new bytes32[](1); - validProof[0] = siblingLeaf; + // Create multiple merkle trees for comprehensive batch testing + _createFirstMerkleTree(); + _createSecondMerkleTree(); + _createThirdMerkleTree(); // Set up the rewards registry for the operator set vm.prank(avsOwner); serviceManager.setRewardsRegistry(operatorSetId, IRewardsRegistry(address(rewardsRegistry))); - // Set the merkle root + // Set all three merkle roots to create a history vm.prank(mockRewardsAgent); rewardsRegistry.updateRewardsMerkleRoot(merkleRoot); + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(secondMerkleRoot); + + vm.prank(mockRewardsAgent); + rewardsRegistry.updateRewardsMerkleRoot(thirdMerkleRoot); + // Add funds to the registry for rewards vm.deal(address(rewardsRegistry), 1000 ether); } + function _createFirstMerkleTree() internal { + // Create first merkle tree + bytes32 leaf = keccak256(abi.encode(operatorAddress, operatorPoints)); + bytes32 siblingLeaf = keccak256(abi.encodePacked("sibling1")); + (bytes32 leftLeaf, bytes32 rightLeaf) = + leaf < siblingLeaf ? (leaf, siblingLeaf) : (siblingLeaf, leaf); + merkleRoot = keccak256(abi.encodePacked(leftLeaf, rightLeaf)); + validProof = new bytes32[](1); + validProof[0] = siblingLeaf; + } + + function _createSecondMerkleTree() internal { + // Create second merkle tree with different points + bytes32 leaf = keccak256(abi.encode(operatorAddress, secondOperatorPoints)); + bytes32 siblingLeaf = keccak256(abi.encodePacked("sibling2")); + (bytes32 leftLeaf, bytes32 rightLeaf) = + leaf < siblingLeaf ? (leaf, siblingLeaf) : (siblingLeaf, leaf); + secondMerkleRoot = keccak256(abi.encodePacked(leftLeaf, rightLeaf)); + secondValidProof = new bytes32[](1); + secondValidProof[0] = siblingLeaf; + } + + function _createThirdMerkleTree() internal { + // Create third merkle tree with different points + bytes32 leaf = keccak256(abi.encode(operatorAddress, thirdOperatorPoints)); + bytes32 siblingLeaf = keccak256(abi.encodePacked("sibling3")); + (bytes32 leftLeaf, bytes32 rightLeaf) = + leaf < siblingLeaf ? (leaf, siblingLeaf) : (siblingLeaf, leaf); + thirdMerkleRoot = keccak256(abi.encodePacked(leftLeaf, rightLeaf)); + thirdValidProof = new bytes32[](1); + thirdValidProof[0] = siblingLeaf; + } + function test_setRewardsRegistry() public { uint32 newOperatorSetId = 2; RewardsRegistry newRewardsRegistry = @@ -94,6 +148,65 @@ contract ServiceManagerRewardsRegistryTest is AVSDeployer { ); } + function test_claimLatestOperatorRewards() public { + uint256 initialBalance = operatorAddress.balance; + + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(true) + ); + + vm.prank(operatorAddress); + vm.expectEmit(true, true, true, true); + emit RewardsClaimedForIndex(operatorAddress, 2, thirdOperatorPoints, thirdOperatorPoints); + + serviceManager.claimLatestOperatorRewards( + operatorSetId, thirdOperatorPoints, thirdValidProof + ); + + assertEq( + operatorAddress.balance, + initialBalance + thirdOperatorPoints, + "Operator should receive correct rewards" + ); + } + + function test_claimLatestOperatorRewards_NoRewardsRegistry() public { + uint32 invalidSetId = 999; + + vm.prank(operatorAddress); + vm.expectRevert( + abi.encodeWithSelector(IServiceManagerErrors.NoRewardsRegistryForOperatorSet.selector) + ); + + serviceManager.claimLatestOperatorRewards(invalidSetId, operatorPoints, validProof); + } + + function test_claimLatestOperatorRewards_AlreadyClaimed() public { + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(true) + ); + + // First claim (uses latest merkle root - index 2) + vm.prank(operatorAddress); + serviceManager.claimLatestOperatorRewards( + operatorSetId, thirdOperatorPoints, thirdValidProof + ); + + // Second claim should fail + vm.prank(operatorAddress); + vm.expectRevert( + abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector) + ); + + serviceManager.claimLatestOperatorRewards( + operatorSetId, thirdOperatorPoints, thirdValidProof + ); + } + function test_claimOperatorRewards() public { uint256 initialBalance = operatorAddress.balance; @@ -105,9 +218,9 @@ contract ServiceManagerRewardsRegistryTest is AVSDeployer { vm.prank(operatorAddress); vm.expectEmit(true, true, true, true); - emit RewardsClaimed(operatorAddress, operatorPoints, operatorPoints); + emit RewardsClaimedForIndex(operatorAddress, 0, operatorPoints, operatorPoints); - serviceManager.claimOperatorRewards(operatorSetId, operatorPoints, validProof); + serviceManager.claimOperatorRewards(operatorSetId, 0, operatorPoints, validProof); assertEq( operatorAddress.balance, @@ -116,15 +229,61 @@ contract ServiceManagerRewardsRegistryTest is AVSDeployer { ); } - function test_claimOperatorRewards_NoRewardsRegistry() public { - uint32 invalidSetId = 999; + function test_claimOperatorRewards_DifferentIndices() public { + uint256 initialBalance = operatorAddress.balance; + + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(true) + ); + + // Claim from index 1 (second merkle root) + vm.prank(operatorAddress); + serviceManager.claimOperatorRewards( + operatorSetId, 1, secondOperatorPoints, secondValidProof + ); + + assertEq( + operatorAddress.balance, + initialBalance + secondOperatorPoints, + "Operator should receive rewards from second root" + ); + + // Claim from index 2 (third merkle root) + vm.prank(operatorAddress); + serviceManager.claimOperatorRewards(operatorSetId, 2, thirdOperatorPoints, thirdValidProof); + + assertEq( + operatorAddress.balance, + initialBalance + secondOperatorPoints + thirdOperatorPoints, + "Operator should receive rewards from both roots" + ); + + // Verify claim status + assertFalse( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 0), "Index 0 should not be claimed" + ); + assertTrue( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 1), "Index 1 should be claimed" + ); + assertTrue( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 2), "Index 2 should be claimed" + ); + } + + function test_claimOperatorRewards_InvalidIndex() public { + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(true) + ); vm.prank(operatorAddress); vm.expectRevert( - abi.encodeWithSelector(IServiceManagerErrors.NoRewardsRegistryForOperatorSet.selector) + abi.encodeWithSelector(IRewardsRegistryErrors.InvalidMerkleRootIndex.selector) ); - - serviceManager.claimOperatorRewards(invalidSetId, operatorPoints, validProof); + serviceManager.claimOperatorRewards(operatorSetId, 999, operatorPoints, validProof); } function test_claimOperatorRewards_AlreadyClaimed() public { @@ -136,15 +295,190 @@ contract ServiceManagerRewardsRegistryTest is AVSDeployer { // First claim vm.prank(operatorAddress); - serviceManager.claimOperatorRewards(operatorSetId, operatorPoints, validProof); + serviceManager.claimOperatorRewards(operatorSetId, 0, operatorPoints, validProof); // Second claim should fail vm.prank(operatorAddress); vm.expectRevert( - abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimed.selector) + abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector) + ); + serviceManager.claimOperatorRewards(operatorSetId, 0, operatorPoints, validProof); + } + + function test_claimOperatorRewardsBatch() public { + // Test claiming from multiple different merkle root indices + uint256[] memory rootIndices = new uint256[](3); + rootIndices[0] = 0; // First merkle root + rootIndices[1] = 1; // Second merkle root + rootIndices[2] = 2; // Third merkle root + + uint256[] memory points = new uint256[](3); + points[0] = operatorPoints; + points[1] = secondOperatorPoints; + points[2] = thirdOperatorPoints; + + bytes32[][] memory proofs = new bytes32[][](3); + proofs[0] = validProof; + proofs[1] = secondValidProof; + proofs[2] = thirdValidProof; + + uint256 expectedTotalRewards = operatorPoints + secondOperatorPoints + thirdOperatorPoints; + uint256 initialBalance = operatorAddress.balance; + + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(true) ); - serviceManager.claimOperatorRewards(operatorSetId, operatorPoints, validProof); + vm.prank(operatorAddress); + vm.expectEmit(true, true, true, true); + emit RewardsBatchClaimedForIndices( + operatorAddress, rootIndices, points, expectedTotalRewards + ); + + serviceManager.claimOperatorRewardsBatch(operatorSetId, rootIndices, points, proofs); + + // Verify final balance includes all rewards + assertEq( + operatorAddress.balance, + initialBalance + expectedTotalRewards, + "Operator should receive rewards from all three claims" + ); + + // Verify all indices are now claimed + assertTrue( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 0), + "Operator should have claimed from index 0" + ); + assertTrue( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 1), + "Operator should have claimed from index 1" + ); + assertTrue( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 2), + "Operator should have claimed from index 2" + ); + } + + function test_claimOperatorRewardsBatch_PartialBatch() public { + // Test claiming from only some of the available merkle roots + uint256[] memory rootIndices = new uint256[](2); + rootIndices[0] = 0; // First merkle root + rootIndices[1] = 2; // Third merkle root (skipping second) + + uint256[] memory points = new uint256[](2); + points[0] = operatorPoints; + points[1] = thirdOperatorPoints; + + bytes32[][] memory proofs = new bytes32[][](2); + proofs[0] = validProof; + proofs[1] = thirdValidProof; + + uint256 expectedTotalRewards = operatorPoints + thirdOperatorPoints; + uint256 initialBalance = operatorAddress.balance; + + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(true) + ); + + vm.prank(operatorAddress); + serviceManager.claimOperatorRewardsBatch(operatorSetId, rootIndices, points, proofs); + + // Verify balance and claim status + assertEq( + operatorAddress.balance, + initialBalance + expectedTotalRewards, + "Operator should receive rewards from claimed indices" + ); + + assertTrue( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 0), + "Operator should have claimed from index 0" + ); + assertFalse( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 1), + "Operator should NOT have claimed from index 1" + ); + assertTrue( + rewardsRegistry.hasClaimedByIndex(operatorAddress, 2), + "Operator should have claimed from index 2" + ); + } + + function test_claimOperatorRewardsBatch_ArrayLengthMismatch() public { + uint256[] memory rootIndices = new uint256[](2); + uint256[] memory points = new uint256[](1); // Wrong length + bytes32[][] memory proofs = new bytes32[][](2); + + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(true) + ); + + vm.prank(operatorAddress); + vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.ArrayLengthMismatch.selector)); + serviceManager.claimOperatorRewardsBatch(operatorSetId, rootIndices, points, proofs); + } + + function test_claimOperatorRewardsBatch_AlreadyClaimedIndex() public { + // First claim from index 1 + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(true) + ); + + vm.prank(operatorAddress); + serviceManager.claimOperatorRewards( + operatorSetId, 1, secondOperatorPoints, secondValidProof + ); + + // Now try to batch claim including the already claimed index 1 + uint256[] memory rootIndices = new uint256[](2); + rootIndices[0] = 0; + rootIndices[1] = 1; // Already claimed + + uint256[] memory points = new uint256[](2); + points[0] = operatorPoints; + points[1] = secondOperatorPoints; + + bytes32[][] memory proofs = new bytes32[][](2); + proofs[0] = validProof; + proofs[1] = secondValidProof; + + vm.prank(operatorAddress); + vm.expectRevert( + abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector) + ); + serviceManager.claimOperatorRewardsBatch(operatorSetId, rootIndices, points, proofs); + } + + function test_claimOperatorRewardsBatch_EmptyBatch() public { + uint256[] memory rootIndices = new uint256[](0); + uint256[] memory points = new uint256[](0); + bytes32[][] memory proofs = new bytes32[][](0); + + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(true) + ); + + uint256 initialBalance = operatorAddress.balance; + + vm.prank(operatorAddress); + serviceManager.claimOperatorRewardsBatch(operatorSetId, rootIndices, points, proofs); + + // Balance should remain unchanged + assertEq( + operatorAddress.balance, + initialBalance, + "Balance should remain unchanged for empty batch" + ); } function test_integration_multipleOperatorSets() public { @@ -165,11 +499,11 @@ contract ServiceManagerRewardsRegistryTest is AVSDeployer { (bytes32 leftLeaf, bytes32 rightLeaf) = secondLeaf < secondSiblingLeaf ? (secondLeaf, secondSiblingLeaf) : (secondSiblingLeaf, secondLeaf); - bytes32 secondMerkleRoot = keccak256(abi.encodePacked(leftLeaf, rightLeaf)); + bytes32 secondRegistryMerkleRoot = keccak256(abi.encodePacked(leftLeaf, rightLeaf)); // Set the merkle root in the second registry vm.prank(mockRewardsAgent); - secondRegistry.updateRewardsMerkleRoot(secondMerkleRoot); + secondRegistry.updateRewardsMerkleRoot(secondRegistryMerkleRoot); // Fund the second registry vm.deal(address(secondRegistry), 1000 ether); @@ -178,7 +512,7 @@ contract ServiceManagerRewardsRegistryTest is AVSDeployer { bytes32[] memory secondProof = new bytes32[](1); secondProof[0] = secondSiblingLeaf; - // Claim from first registry + // Claim from first registry (uses latest merkle root - index 2) uint256 initialBalance = operatorAddress.balance; vm.mockCall( address(allocationManager), @@ -186,19 +520,21 @@ contract ServiceManagerRewardsRegistryTest is AVSDeployer { abi.encode(true) ); vm.prank(operatorAddress); - serviceManager.claimOperatorRewards(operatorSetId, operatorPoints, validProof); + serviceManager.claimLatestOperatorRewards( + operatorSetId, thirdOperatorPoints, thirdValidProof + ); // Use latest root // Verify balance after first claim assertEq( operatorAddress.balance, - initialBalance + operatorPoints, + initialBalance + thirdOperatorPoints, "Operator should receive correct rewards from first registry" ); // Claim from second registry uint256 balanceAfterFirstClaim = operatorAddress.balance; vm.prank(operatorAddress); - serviceManager.claimOperatorRewards(secondOperatorSetId, operatorPoints, secondProof); + serviceManager.claimLatestOperatorRewards(secondOperatorSetId, operatorPoints, secondProof); // Verify balance after second claim assertEq( @@ -207,4 +543,53 @@ contract ServiceManagerRewardsRegistryTest is AVSDeployer { "Operator should receive correct rewards from second registry" ); } + + function test_claimLatestOperatorRewards_NotInOperatorSet() public { + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(false) // Operator is NOT in the set + ); + + vm.prank(operatorAddress); + vm.expectRevert( + abi.encodeWithSelector(IServiceManagerErrors.OperatorNotInOperatorSet.selector) + ); + serviceManager.claimLatestOperatorRewards(operatorSetId, operatorPoints, validProof); + } + + function test_claimOperatorRewards_NotInOperatorSet() public { + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(false) // Operator is NOT in the set + ); + + vm.prank(operatorAddress); + vm.expectRevert( + abi.encodeWithSelector(IServiceManagerErrors.OperatorNotInOperatorSet.selector) + ); + serviceManager.claimOperatorRewards(operatorSetId, 0, operatorPoints, validProof); + } + + function test_claimOperatorRewardsBatch_NotInOperatorSet() public { + uint256[] memory rootIndices = new uint256[](1); + rootIndices[0] = 0; + uint256[] memory points = new uint256[](1); + points[0] = operatorPoints; + bytes32[][] memory proofs = new bytes32[][](1); + proofs[0] = validProof; + + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(false) // Operator is NOT in the set + ); + + vm.prank(operatorAddress); + vm.expectRevert( + abi.encodeWithSelector(IServiceManagerErrors.OperatorNotInOperatorSet.selector) + ); + serviceManager.claimOperatorRewardsBatch(operatorSetId, rootIndices, points, proofs); + } } diff --git a/contracts/test/SnowbridgeIntegration.t.sol b/contracts/test/SnowbridgeIntegration.t.sol index 161bf1d1..8dce1e19 100644 --- a/contracts/test/SnowbridgeIntegration.t.sol +++ b/contracts/test/SnowbridgeIntegration.t.sol @@ -101,10 +101,12 @@ contract SnowbridgeIntegrationTest is SnowbridgeAndAVSDeployer { ); vm.startPrank(_validatorAddresses[0]); vm.expectEmit(address(rewardsRegistry)); - emit IRewardsRegistryEvents.RewardsClaimed( - _validatorAddresses[0], _validatorPoints[0], uint256(_validatorPoints[0]) + emit IRewardsRegistryEvents.RewardsClaimedForIndex( + _validatorAddresses[0], 0, _validatorPoints[0], uint256(_validatorPoints[0]) + ); + serviceManager.claimLatestOperatorRewards( + 0, _validatorPoints[0], rewardsProofFirstValidator ); - serviceManager.claimOperatorRewards(0, _validatorPoints[0], rewardsProofFirstValidator); vm.stopPrank(); // Check that the validator has received the rewards. @@ -121,10 +123,10 @@ contract SnowbridgeIntegrationTest is SnowbridgeAndAVSDeployer { // Claim rewards for the last validator. vm.startPrank(_validatorAddresses[9]); vm.expectEmit(address(rewardsRegistry)); - emit IRewardsRegistryEvents.RewardsClaimed( - _validatorAddresses[9], _validatorPoints[9], uint256(_validatorPoints[9]) + emit IRewardsRegistryEvents.RewardsClaimedForIndex( + _validatorAddresses[9], 0, _validatorPoints[9], uint256(_validatorPoints[9]) ); - serviceManager.claimOperatorRewards(0, _validatorPoints[9], rewardsProofLastValidator); + serviceManager.claimLatestOperatorRewards(0, _validatorPoints[9], rewardsProofLastValidator); vm.stopPrank(); // Check that the last validator has received the rewards. diff --git a/test/contract-bindings/generated.ts b/test/contract-bindings/generated.ts index b328e736..d98da537 100644 --- a/test/contract-bindings/generated.ts +++ b/test/contract-bindings/generated.ts @@ -2105,10 +2105,34 @@ export const dataHavenServiceManagerAbi = [ { name: 'operatorPoints', internalType: 'uint256', type: 'uint256' }, { name: 'proof', internalType: 'bytes32[]', type: 'bytes32[]' }, ], + name: 'claimLatestOperatorRewards', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { name: 'operatorSetId', internalType: 'uint32', type: 'uint32' }, + { name: 'rootIndex', internalType: 'uint256', type: 'uint256' }, + { name: 'operatorPoints', internalType: 'uint256', type: 'uint256' }, + { name: 'proof', internalType: 'bytes32[]', type: 'bytes32[]' }, + ], name: 'claimOperatorRewards', outputs: [], stateMutability: 'nonpayable', }, + { + type: 'function', + inputs: [ + { name: 'operatorSetId', internalType: 'uint32', type: 'uint32' }, + { name: 'rootIndices', internalType: 'uint256[]', type: 'uint256[]' }, + { name: 'operatorPoints', internalType: 'uint256[]', type: 'uint256[]' }, + { name: 'proofs', internalType: 'bytes32[][]', type: 'bytes32[][]' }, + ], + name: 'claimOperatorRewardsBatch', + outputs: [], + stateMutability: 'nonpayable', + }, { type: 'function', inputs: [ @@ -8113,24 +8137,89 @@ export const rewardsRegistryAbi = [ { name: 'operatorPoints', internalType: 'uint256', type: 'uint256' }, { name: 'proof', internalType: 'bytes32[]', type: 'bytes32[]' }, ], + name: 'claimLatestRewards', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { name: 'operatorAddress', internalType: 'address', type: 'address' }, + { name: 'rootIndex', internalType: 'uint256', type: 'uint256' }, + { name: 'operatorPoints', internalType: 'uint256', type: 'uint256' }, + { name: 'proof', internalType: 'bytes32[]', type: 'bytes32[]' }, + ], name: 'claimRewards', outputs: [], stateMutability: 'nonpayable', }, + { + type: 'function', + inputs: [ + { name: 'operatorAddress', internalType: 'address', type: 'address' }, + { name: 'rootIndices', internalType: 'uint256[]', type: 'uint256[]' }, + { name: 'operatorPoints', internalType: 'uint256[]', type: 'uint256[]' }, + { name: 'proofs', internalType: 'bytes32[][]', type: 'bytes32[][]' }, + ], + name: 'claimRewardsBatch', + outputs: [], + stateMutability: 'nonpayable', + }, { type: 'function', inputs: [], - name: 'lastRewardsMerkleRoot', + name: 'getLatestMerkleRoot', outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], stateMutability: 'view', }, { type: 'function', - inputs: [{ name: '', internalType: 'address', type: 'address' }], - name: 'operatorToLastClaimedRoot', + inputs: [], + name: 'getLatestMerkleRootIndex', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'index', internalType: 'uint256', type: 'uint256' }], + name: 'getMerkleRootByIndex', outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], stateMutability: 'view', }, + { + type: 'function', + inputs: [], + name: 'getMerkleRootHistoryLength', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'operatorAddress', internalType: 'address', type: 'address' }, + { name: 'rootIndex', internalType: 'uint256', type: 'uint256' }, + ], + name: 'hasClaimedByIndex', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + name: 'merkleRootHistory', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: '', internalType: 'address', type: 'address' }, + { name: '', internalType: 'uint256', type: 'uint256' }, + ], + name: 'operatorClaimedByIndex', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, { type: 'function', inputs: [], @@ -8166,6 +8255,43 @@ export const rewardsRegistryAbi = [ type: 'address', indexed: true, }, + { + name: 'rootIndices', + internalType: 'uint256[]', + type: 'uint256[]', + indexed: false, + }, + { + name: 'points', + internalType: 'uint256[]', + type: 'uint256[]', + indexed: false, + }, + { + name: 'totalRewardsAmount', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + ], + name: 'RewardsBatchClaimedForIndices', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'operatorAddress', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'rootIndex', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, { name: 'points', internalType: 'uint256', @@ -8179,7 +8305,7 @@ export const rewardsRegistryAbi = [ indexed: false, }, ], - name: 'RewardsClaimed', + name: 'RewardsClaimedForIndex', }, { type: 'event', @@ -8197,13 +8323,21 @@ export const rewardsRegistryAbi = [ type: 'bytes32', indexed: false, }, + { + name: 'newRootIndex', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, ], name: 'RewardsMerkleRootUpdated', }, + { type: 'error', inputs: [], name: 'ArrayLengthMismatch' }, { type: 'error', inputs: [], name: 'InvalidMerkleProof' }, + { type: 'error', inputs: [], name: 'InvalidMerkleRootIndex' }, { type: 'error', inputs: [], name: 'OnlyAVS' }, { type: 'error', inputs: [], name: 'OnlyRewardsAgent' }, - { type: 'error', inputs: [], name: 'RewardsAlreadyClaimed' }, + { type: 'error', inputs: [], name: 'RewardsAlreadyClaimedForIndex' }, { type: 'error', inputs: [], name: 'RewardsMerkleRootNotSet' }, { type: 'error', inputs: [], name: 'RewardsTransferFailed' }, ] as const @@ -11288,6 +11422,15 @@ export const writeDataHavenServiceManagerAddValidatorToAllowlist = functionName: 'addValidatorToAllowlist', }) +/** + * Wraps __{@link writeContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"claimLatestOperatorRewards"` + */ +export const writeDataHavenServiceManagerClaimLatestOperatorRewards = + /*#__PURE__*/ createWriteContract({ + abi: dataHavenServiceManagerAbi, + functionName: 'claimLatestOperatorRewards', + }) + /** * Wraps __{@link writeContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"claimOperatorRewards"` */ @@ -11297,6 +11440,15 @@ export const writeDataHavenServiceManagerClaimOperatorRewards = functionName: 'claimOperatorRewards', }) +/** + * Wraps __{@link writeContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"claimOperatorRewardsBatch"` + */ +export const writeDataHavenServiceManagerClaimOperatorRewardsBatch = + /*#__PURE__*/ createWriteContract({ + abi: dataHavenServiceManagerAbi, + functionName: 'claimOperatorRewardsBatch', + }) + /** * Wraps __{@link writeContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"createAVSRewardsSubmission"` */ @@ -11672,6 +11824,15 @@ export const simulateDataHavenServiceManagerAddValidatorToAllowlist = functionName: 'addValidatorToAllowlist', }) +/** + * Wraps __{@link simulateContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"claimLatestOperatorRewards"` + */ +export const simulateDataHavenServiceManagerClaimLatestOperatorRewards = + /*#__PURE__*/ createSimulateContract({ + abi: dataHavenServiceManagerAbi, + functionName: 'claimLatestOperatorRewards', + }) + /** * Wraps __{@link simulateContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"claimOperatorRewards"` */ @@ -11681,6 +11842,15 @@ export const simulateDataHavenServiceManagerClaimOperatorRewards = functionName: 'claimOperatorRewards', }) +/** + * Wraps __{@link simulateContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"claimOperatorRewardsBatch"` + */ +export const simulateDataHavenServiceManagerClaimOperatorRewardsBatch = + /*#__PURE__*/ createSimulateContract({ + abi: dataHavenServiceManagerAbi, + functionName: 'claimOperatorRewardsBatch', + }) + /** * Wraps __{@link simulateContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"createAVSRewardsSubmission"` */ @@ -15954,21 +16124,66 @@ export const readRewardsRegistryAvs = /*#__PURE__*/ createReadContract({ }) /** - * Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"lastRewardsMerkleRoot"` + * Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"getLatestMerkleRoot"` */ -export const readRewardsRegistryLastRewardsMerkleRoot = +export const readRewardsRegistryGetLatestMerkleRoot = /*#__PURE__*/ createReadContract({ abi: rewardsRegistryAbi, - functionName: 'lastRewardsMerkleRoot', + functionName: 'getLatestMerkleRoot', }) /** - * Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"operatorToLastClaimedRoot"` + * Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"getLatestMerkleRootIndex"` */ -export const readRewardsRegistryOperatorToLastClaimedRoot = +export const readRewardsRegistryGetLatestMerkleRootIndex = /*#__PURE__*/ createReadContract({ abi: rewardsRegistryAbi, - functionName: 'operatorToLastClaimedRoot', + functionName: 'getLatestMerkleRootIndex', + }) + +/** + * Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"getMerkleRootByIndex"` + */ +export const readRewardsRegistryGetMerkleRootByIndex = + /*#__PURE__*/ createReadContract({ + abi: rewardsRegistryAbi, + functionName: 'getMerkleRootByIndex', + }) + +/** + * Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"getMerkleRootHistoryLength"` + */ +export const readRewardsRegistryGetMerkleRootHistoryLength = + /*#__PURE__*/ createReadContract({ + abi: rewardsRegistryAbi, + functionName: 'getMerkleRootHistoryLength', + }) + +/** + * Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"hasClaimedByIndex"` + */ +export const readRewardsRegistryHasClaimedByIndex = + /*#__PURE__*/ createReadContract({ + abi: rewardsRegistryAbi, + functionName: 'hasClaimedByIndex', + }) + +/** + * Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"merkleRootHistory"` + */ +export const readRewardsRegistryMerkleRootHistory = + /*#__PURE__*/ createReadContract({ + abi: rewardsRegistryAbi, + functionName: 'merkleRootHistory', + }) + +/** + * Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"operatorClaimedByIndex"` + */ +export const readRewardsRegistryOperatorClaimedByIndex = + /*#__PURE__*/ createReadContract({ + abi: rewardsRegistryAbi, + functionName: 'operatorClaimedByIndex', }) /** @@ -15985,6 +16200,15 @@ export const writeRewardsRegistry = /*#__PURE__*/ createWriteContract({ abi: rewardsRegistryAbi, }) +/** + * Wraps __{@link writeContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"claimLatestRewards"` + */ +export const writeRewardsRegistryClaimLatestRewards = + /*#__PURE__*/ createWriteContract({ + abi: rewardsRegistryAbi, + functionName: 'claimLatestRewards', + }) + /** * Wraps __{@link writeContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"claimRewards"` */ @@ -15994,6 +16218,15 @@ export const writeRewardsRegistryClaimRewards = functionName: 'claimRewards', }) +/** + * Wraps __{@link writeContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"claimRewardsBatch"` + */ +export const writeRewardsRegistryClaimRewardsBatch = + /*#__PURE__*/ createWriteContract({ + abi: rewardsRegistryAbi, + functionName: 'claimRewardsBatch', + }) + /** * Wraps __{@link writeContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"setRewardsAgent"` */ @@ -16019,6 +16252,15 @@ export const simulateRewardsRegistry = /*#__PURE__*/ createSimulateContract({ abi: rewardsRegistryAbi, }) +/** + * Wraps __{@link simulateContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"claimLatestRewards"` + */ +export const simulateRewardsRegistryClaimLatestRewards = + /*#__PURE__*/ createSimulateContract({ + abi: rewardsRegistryAbi, + functionName: 'claimLatestRewards', + }) + /** * Wraps __{@link simulateContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"claimRewards"` */ @@ -16028,6 +16270,15 @@ export const simulateRewardsRegistryClaimRewards = functionName: 'claimRewards', }) +/** + * Wraps __{@link simulateContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"claimRewardsBatch"` + */ +export const simulateRewardsRegistryClaimRewardsBatch = + /*#__PURE__*/ createSimulateContract({ + abi: rewardsRegistryAbi, + functionName: 'claimRewardsBatch', + }) + /** * Wraps __{@link simulateContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"setRewardsAgent"` */ @@ -16054,12 +16305,21 @@ export const watchRewardsRegistryEvent = /*#__PURE__*/ createWatchContractEvent( ) /** - * Wraps __{@link watchContractEvent}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `eventName` set to `"RewardsClaimed"` + * Wraps __{@link watchContractEvent}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `eventName` set to `"RewardsBatchClaimedForIndices"` */ -export const watchRewardsRegistryRewardsClaimedEvent = +export const watchRewardsRegistryRewardsBatchClaimedForIndicesEvent = /*#__PURE__*/ createWatchContractEvent({ abi: rewardsRegistryAbi, - eventName: 'RewardsClaimed', + eventName: 'RewardsBatchClaimedForIndices', + }) + +/** + * Wraps __{@link watchContractEvent}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `eventName` set to `"RewardsClaimedForIndex"` + */ +export const watchRewardsRegistryRewardsClaimedForIndexEvent = + /*#__PURE__*/ createWatchContractEvent({ + abi: rewardsRegistryAbi, + eventName: 'RewardsClaimedForIndex', }) /**