From e8970a2b5f45c57851b2ccf5462d63446c1d517f Mon Sep 17 00:00:00 2001 From: Tobi Demeco <50408393+TDemeco@users.noreply.github.com> Date: Thu, 10 Jul 2025 03:47:39 -0300 Subject: [PATCH] feat: :sparkles: make `RewardsRegistry` keep a history of roots and claim status (#106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR implements a comprehensive overhaul of the `RewardsRegistry` contract to maintain complete history of reward merkle roots while providing index-based claim tracking for operators. The new architecture enables operators to claim rewards from any historical merkle root instead of only the latest one. To do so, it: - Adds the `merkleRootHistory` storage array to the contract, in which we keep all rewards roots that ever came from the DataHaven side. - Adds the `operatorClaimedByIndex` storage map to the contract, in which we keep track, for each validator and root index, if it has claimed it or not. - This works even for new validators, since theoretically with this system you could argue they could claim older roots that they were not a part of which would be catastrophic, but they could never draft a correct proof for those to claim them. - Keeps some of the interface from before the overhaul, to have quick access to the latest rewards merkle root through `getLatestMerkleRoot()` and to claim rewards for it with `claimRewards()`. This is because the expected behaviour is for validators to claim their rewards every era. - Adds a way to batch claim rewards with `claimRewardsBatch()`. This function allows a validator to claim rewards for multiple root indices in one call by providing multiple proofs, useful if the validator has fallen behind claims and has to catch up, although special care will have to be taken by it to avoid reaching the gas limit of a transaction. ## Storage Efficiency Analysis One might think this solution is not as storage-efficient as other solutions that we can think of (I even had two other alternatives in mind as well), but a simple back-of-the-envelope calculation gives us peace of mind that the impact of this solution on the overal state size of the chain is negligible: ### Assumptions (Worst Case Scenario): - 1,000 validators (actual estimate for DataHaven: ~50/100 validators) - 6-hour eras (most-likely scenario, following what Polkadot does: ~24-hour eras) - Which means 4 merkle root updates per day ### Annual Storage Requirements: - Merkle Root History: **46,720 bytes/year** - 4 roots/day × 32 bytes/root × 365 days/year = 46,720 bytes/year - Operator Claim Tracking: **~1.46 MB/year** - 1,000 operators × 1 boolean/(operator * root index) × 1 byte/boolean × 4 root indices/day × 365 days/year = 1,460,000 bytes/year - **Total: ~1.5 MB/year** This represents negligible storage overhead compared to the significant operational benefits gained. ## TODO Since we want to allow the operators/validators to only have to interact with the AVS contract (that's why the `claimRewards` functions have the `onlyAVS` modifier), we still have to: - [x] Add the required functions to the AVS to allow operators to claim their rewards. - [x] Adds comprehensive unit tests for them. --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Co-authored-by: Ahmad Kaouk --- contracts/src/interfaces/IRewardsRegistry.sol | 107 ++++- contracts/src/interfaces/IServiceManager.sol | 30 +- contracts/src/middleware/RewardsRegistry.sol | 179 ++++++- .../src/middleware/RewardsRegistryStorage.sol | 8 +- .../src/middleware/ServiceManagerBase.sol | 56 ++- contracts/test/RewardsRegistry.t.sol | 379 +++++++++++++-- .../test/ServiceManagerRewardsRegistry.t.sol | 437 ++++++++++++++++-- contracts/test/SnowbridgeIntegration.t.sol | 14 +- test/contract-bindings/generated.ts | 288 +++++++++++- 9 files changed, 1390 insertions(+), 108 deletions(-) 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', }) /**