Skip to content

Commit d20735e

Browse files
RembrandtKclaude
andcommitted
feat: add reclaim addresses for denied rewards in RewardsManager
Add two new addresses to RewardsManager V6 storage that allow denied rewards to be minted to designated reclaim addresses instead of being lost: - indexerEligibilityReclaimAddress: receives tokens denied due to indexer eligibility - subgraphDeniedReclaimAddress: receives tokens denied due to subgraph denylist Both addresses default to zero (disabled) and can be set by governor. When set to non-zero, denied rewards are minted to these addresses instead of being burned. Original denial events are always emitted, with additional reclaim events when minting occurs. Changes: - Add setIndexerEligibilityReclaimAddress() and setSubgraphDeniedReclaimAddress() setter functions - Add corresponding events for address changes and reclaim operations - Refactor takeRewards() denial logic into helper functions to maintain code quality - Update IRewardsManager interface (interface ID: 0x731e44f0) - Add comprehensive test coverage (12 tests) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 59725c5 commit d20735e

File tree

6 files changed

+455
-19
lines changed

6 files changed

+455
-19
lines changed

packages/contracts/contracts/rewards/RewardsManager.sol

Lines changed: 130 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,48 @@ contract RewardsManager is RewardsManagerV6Storage, GraphUpgradeable, IERC165, I
108108
address indexed newRewardsEligibilityOracle
109109
);
110110

111+
/**
112+
* @notice Emitted when the eligibility reclaim address is set
113+
* @param oldEligibilityReclaimAddress Previous eligibility reclaim address
114+
* @param newEligibilityReclaimAddress New eligibility reclaim address
115+
*/
116+
event EligibilityReclaimAddressSet(
117+
address indexed oldEligibilityReclaimAddress,
118+
address indexed newEligibilityReclaimAddress
119+
);
120+
121+
/**
122+
* @notice Emitted when the subgraph reclaim address is set
123+
* @param oldSubgraphReclaimAddress Previous subgraph reclaim address
124+
* @param newSubgraphReclaimAddress New subgraph reclaim address
125+
*/
126+
event SubgraphReclaimAddressSet(
127+
address indexed oldSubgraphReclaimAddress,
128+
address indexed newSubgraphReclaimAddress
129+
);
130+
131+
/**
132+
* @notice Emitted when denied rewards are reclaimed due to eligibility
133+
* @param indexer Address of the indexer whose rewards were denied
134+
* @param allocationID Address of the allocation
135+
* @param amount Amount of rewards reclaimed
136+
*/
137+
event RewardsReclaimedDueToEligibility(address indexed indexer, address indexed allocationID, uint256 amount);
138+
139+
/**
140+
* @notice Emitted when denied rewards are reclaimed due to subgraph denylist
141+
* @param indexer Address of the indexer whose rewards were denied
142+
* @param allocationID Address of the allocation
143+
* @param subgraphDeploymentID Subgraph deployment ID that was denied
144+
* @param amount Amount of rewards reclaimed
145+
*/
146+
event RewardsReclaimedDueToSubgraphDenylist(
147+
address indexed indexer,
148+
address indexed allocationID,
149+
bytes32 indexed subgraphDeploymentID,
150+
uint256 amount
151+
);
152+
111153
// -- Modifiers --
112154

113155
/**
@@ -264,6 +306,30 @@ contract RewardsManager is RewardsManagerV6Storage, GraphUpgradeable, IERC165, I
264306
}
265307
}
266308

309+
/**
310+
* @inheritdoc IRewardsManager
311+
* @dev Set to zero address to disable eligibility reclaim functionality
312+
*/
313+
function setIndexerEligibilityReclaimAddress(address newEligibilityReclaimAddress) external override onlyGovernor {
314+
if (indexerEligibilityReclaimAddress != newEligibilityReclaimAddress) {
315+
address oldEligibilityReclaimAddress = indexerEligibilityReclaimAddress;
316+
indexerEligibilityReclaimAddress = newEligibilityReclaimAddress;
317+
emit EligibilityReclaimAddressSet(oldEligibilityReclaimAddress, newEligibilityReclaimAddress);
318+
}
319+
}
320+
321+
/**
322+
* @inheritdoc IRewardsManager
323+
* @dev Set to zero address to disable subgraph reclaim functionality
324+
*/
325+
function setSubgraphDeniedReclaimAddress(address newSubgraphReclaimAddress) external override onlyGovernor {
326+
if (subgraphDeniedReclaimAddress != newSubgraphReclaimAddress) {
327+
address oldSubgraphReclaimAddress = subgraphDeniedReclaimAddress;
328+
subgraphDeniedReclaimAddress = newSubgraphReclaimAddress;
329+
emit SubgraphReclaimAddressSet(oldSubgraphReclaimAddress, newSubgraphReclaimAddress);
330+
}
331+
}
332+
267333
// -- Denylist --
268334

269335
/**
@@ -494,6 +560,60 @@ contract RewardsManager is RewardsManagerV6Storage, GraphUpgradeable, IERC165, I
494560
return newAccrued.mul(_tokens).div(FIXED_POINT_SCALING_FACTOR);
495561
}
496562

563+
/**
564+
* @notice Checks for and handles denial and reclaim of rewards due to subgraph deny list
565+
* @dev If denied, emits RewardsDenied event and mints to reclaim address if configured
566+
* @param indexer Address of the indexer
567+
* @param allocationID Address of the allocation
568+
* @param subgraphDeploymentID Subgraph deployment ID
569+
* @param rewards Amount of rewards that would be distributed
570+
* @return True if rewards are denied, false otherwise
571+
*/
572+
function _rewardsDeniedDueToSubgraphDenyList(
573+
address indexer,
574+
address allocationID,
575+
bytes32 subgraphDeploymentID,
576+
uint256 rewards
577+
) private returns (bool) {
578+
if (isDenied(subgraphDeploymentID)) {
579+
emit RewardsDenied(indexer, allocationID);
580+
581+
// If a reclaim address is set, mint the denied rewards there
582+
if (0 < rewards && subgraphDeniedReclaimAddress != address(0)) {
583+
graphToken().mint(subgraphDeniedReclaimAddress, rewards);
584+
emit RewardsReclaimedDueToSubgraphDenylist(indexer, allocationID, subgraphDeploymentID, rewards);
585+
}
586+
return true;
587+
}
588+
return false;
589+
}
590+
591+
/**
592+
* @notice Checks for and handles denial and reclaim of rewards due to indexer eligibility
593+
* @dev If denied, emits RewardsDeniedDueToEligibility event and mints to reclaim address if configured
594+
* @param indexer Address of the indexer
595+
* @param allocationID Address of the allocation
596+
* @param rewards Amount of rewards that would be distributed
597+
* @return True if rewards are denied, false otherwise
598+
*/
599+
function _rewardsDeniedDueToIndexerEligibility(
600+
address indexer,
601+
address allocationID,
602+
uint256 rewards
603+
) private returns (bool) {
604+
if (address(rewardsEligibilityOracle) != address(0) && !rewardsEligibilityOracle.isEligible(indexer)) {
605+
emit RewardsDeniedDueToEligibility(indexer, allocationID, rewards);
606+
607+
// If a reclaim address is set, mint the denied rewards there
608+
if (0 < rewards && indexerEligibilityReclaimAddress != address(0)) {
609+
graphToken().mint(indexerEligibilityReclaimAddress, rewards);
610+
emit RewardsReclaimedDueToEligibility(indexer, allocationID, rewards);
611+
}
612+
return true;
613+
}
614+
return false;
615+
}
616+
497617
/**
498618
* @inheritdoc IRewardsManager
499619
* @dev This function can only be called by an authorized rewards issuer which are
@@ -518,31 +638,24 @@ contract RewardsManager is RewardsManagerV6Storage, GraphUpgradeable, IERC165, I
518638

519639
uint256 updatedAccRewardsPerAllocatedToken = onSubgraphAllocationUpdate(subgraphDeploymentID);
520640

521-
// Do not do rewards on denied subgraph deployments ID
522-
if (isDenied(subgraphDeploymentID)) {
523-
emit RewardsDenied(indexer, _allocationID);
524-
return 0;
525-
}
526-
527641
uint256 rewards = 0;
528642
if (isActive) {
529643
// Calculate rewards accrued by this allocation
530644
rewards = accRewardsPending.add(
531645
_calcRewards(tokens, accRewardsPerAllocatedToken, updatedAccRewardsPerAllocatedToken)
532646
);
647+
}
533648

534-
// Do not reward if indexer is not eligible based on rewards eligibility
535-
if (address(rewardsEligibilityOracle) != address(0) && !rewardsEligibilityOracle.isEligible(indexer)) {
536-
emit RewardsDeniedDueToEligibility(indexer, _allocationID, rewards);
537-
return 0;
538-
}
649+
if (_rewardsDeniedDueToSubgraphDenyList(indexer, _allocationID, subgraphDeploymentID, rewards)) return 0;
539650

540-
if (rewards > 0) {
541-
// Mint directly to rewards issuer for the reward amount
542-
// The rewards issuer contract will do bookkeeping of the reward and
543-
// assign in proportion to each stakeholder incentive
544-
graphToken().mint(rewardsIssuer, rewards);
545-
}
651+
if (_rewardsDeniedDueToIndexerEligibility(indexer, _allocationID, rewards)) return 0;
652+
653+
// Mint rewards to the rewards issuer
654+
if (rewards > 0) {
655+
// Mint directly to rewards issuer for the reward amount
656+
// The rewards issuer contract will do bookkeeping of the reward and
657+
// assign in proportion to each stakeholder incentive
658+
graphToken().mint(rewardsIssuer, rewards);
546659
}
547660

548661
emit HorizonRewardsAssigned(indexer, _allocationID, rewards);

packages/contracts/contracts/rewards/RewardsManagerStorage.sol

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,15 @@ contract RewardsManagerV5Storage is RewardsManagerV4Storage {
8383
* @title RewardsManagerV6Storage
8484
* @author Edge & Node
8585
* @notice Storage layout for RewardsManager V6
86-
* Includes support for Rewards Eligibility Oracle and Issuance Allocator.
86+
* Includes support for Rewards Eligibility Oracle, Issuance Allocator, and Reclaim Addresses.
8787
*/
8888
contract RewardsManagerV6Storage is RewardsManagerV5Storage {
8989
/// @notice Address of the rewards eligibility oracle contract
9090
IRewardsEligibility public rewardsEligibilityOracle;
9191
/// @notice Address of the issuance allocator
9292
IIssuanceAllocationDistribution public issuanceAllocator;
93+
/// @notice Address to mint tokens that would be denied due to eligibility
94+
address public indexerEligibilityReclaimAddress;
95+
/// @notice Address to mint tokens that would be denied due to subgraph denylist
96+
address public subgraphDeniedReclaimAddress;
9397
}

packages/contracts/test/tests/unit/rewards/rewards-interface.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ describe('RewardsManager interfaces', () => {
5757
})
5858

5959
it('IRewardsManager should have stable interface ID', () => {
60-
expect(IRewardsManager__factory.interfaceId).to.equal('0xa31d8306')
60+
expect(IRewardsManager__factory.interfaceId).to.equal('0x731e44f0')
6161
})
6262
})
6363

0 commit comments

Comments
 (0)