Skip to content

Commit ec0c984

Browse files
committed
feat: baseline issuance package
1 parent d863720 commit ec0c984

40 files changed

+4507
-209
lines changed

packages/contracts/contracts/rewards/RewardsManager.sol

Lines changed: 138 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,19 @@ pragma abicoder v2;
77
// solhint-disable gas-increment-by-one, gas-indexed-events, gas-small-strings, gas-strict-inequalities
88

99
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
10+
import { IERC165 } from "@openzeppelin/contracts/introspection/IERC165.sol";
1011

1112
import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol";
1213
import { Managed } from "../governance/Managed.sol";
1314
import { MathUtils } from "../staking/libs/MathUtils.sol";
1415
import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol";
1516

16-
import { RewardsManagerV5Storage } from "./RewardsManagerStorage.sol";
17+
import { RewardsManagerV6Storage } from "./RewardsManagerStorage.sol";
1718
import { IRewardsIssuer } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol";
1819
import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol";
20+
import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol";
21+
import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol";
22+
import { IRewardsEligibility } from "@graphprotocol/interfaces/contracts/issuance/eligibility/IRewardsEligibility.sol";
1923

2024
/**
2125
* @title Rewards Manager Contract
@@ -27,6 +31,10 @@ import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/r
2731
* total rewards for the Subgraph are split up for each Indexer based on much they have Staked on
2832
* that Subgraph.
2933
*
34+
* @dev If an `issuanceAllocator` is set, it is used to determine the amount of GRT to be issued per block.
35+
* Otherwise, the `issuancePerBlock` variable is used. In relation to the IssuanceAllocator, this contract
36+
* is a self-minting target responsible for directly minting allocated GRT.
37+
*
3038
* Note:
3139
* The contract provides getter functions to query the state of accrued rewards:
3240
* - getAccRewardsPerSignal
@@ -37,7 +45,7 @@ import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/r
3745
* until the actual takeRewards function is called.
3846
* custom:security-contact Please email security+contracts@ thegraph.com (remove space) if you find any bugs. We might have an active bug bounty program.
3947
*/
40-
contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsManager {
48+
contract RewardsManager is RewardsManagerV6Storage, GraphUpgradeable, IERC165, IRewardsManager, IIssuanceTarget {
4149
using SafeMath for uint256;
4250

4351
/// @dev Fixed point scaling factor used for decimals in reward calculations
@@ -61,6 +69,14 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
6169
*/
6270
event RewardsDenied(address indexed indexer, address indexed allocationID);
6371

72+
/**
73+
* @notice Emitted when rewards are denied to an indexer due to eligibility
74+
* @param indexer Address of the indexer being denied rewards
75+
* @param allocationID Address of the allocation being denied rewards
76+
* @param amount Amount of rewards that would have been assigned
77+
*/
78+
event RewardsDeniedDueToEligibility(address indexed indexer, address indexed allocationID, uint256 amount);
79+
6480
/**
6581
* @notice Emitted when a subgraph is denied for claiming rewards
6682
* @param subgraphDeploymentID Subgraph deployment ID being denied
@@ -75,6 +91,23 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
7591
*/
7692
event SubgraphServiceSet(address indexed oldSubgraphService, address indexed newSubgraphService);
7793

94+
/**
95+
* @notice Emitted when the issuance allocator is set
96+
* @param oldIssuanceAllocator Previous issuance allocator address
97+
* @param newIssuanceAllocator New issuance allocator address
98+
*/
99+
event IssuanceAllocatorSet(address indexed oldIssuanceAllocator, address indexed newIssuanceAllocator);
100+
101+
/**
102+
* @notice Emitted when the rewards eligibility oracle contract is set
103+
* @param oldRewardsEligibilityOracle Previous rewards eligibility oracle address
104+
* @param newRewardsEligibilityOracle New rewards eligibility oracle address
105+
*/
106+
event RewardsEligibilityOracleSet(
107+
address indexed oldRewardsEligibilityOracle,
108+
address indexed newRewardsEligibilityOracle
109+
);
110+
78111
// -- Modifiers --
79112

80113
/**
@@ -93,12 +126,27 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
93126
Managed._initialize(_controller);
94127
}
95128

129+
/**
130+
* @inheritdoc IERC165
131+
* @dev Implements ERC165 interface detection
132+
* Returns true if this contract implements the interface defined by interfaceId.
133+
* See: https://eips.ethereum.org/EIPS/eip-165
134+
*/
135+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
136+
return
137+
interfaceId == type(IERC165).interfaceId ||
138+
interfaceId == type(IIssuanceTarget).interfaceId ||
139+
interfaceId == type(IRewardsManager).interfaceId;
140+
}
141+
96142
// -- Config --
97143

98144
/**
99145
* @inheritdoc IRewardsManager
146+
* @dev When an IssuanceAllocator is set, the effective issuance will be determined by the allocator,
147+
* but this local value can still be updated for cases when the allocator is later removed.
100148
*
101-
* @dev The issuance is defined as a fixed amount of rewards per block in GRT.
149+
* The issuance is defined as a fixed amount of rewards per block in GRT.
102150
* Whenever this function is called in layer 2, the updateL2MintAllowance function
103151
* _must_ be called on the L1GraphTokenGateway in L1, to ensure the bridge can mint the
104152
* right amount of tokens.
@@ -152,6 +200,70 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
152200
emit SubgraphServiceSet(oldSubgraphService, _subgraphService);
153201
}
154202

203+
/**
204+
* @inheritdoc IIssuanceTarget
205+
* @dev This function facilitates upgrades by providing a standard way for targets
206+
* to change their allocator. Only the governor can call this function.
207+
* Note that the IssuanceAllocator can be set to the zero address to disable use of an allocator, and
208+
* use the local `issuancePerBlock` variable instead to control issuance.
209+
*/
210+
function setIssuanceAllocator(address newIssuanceAllocator) external override onlyGovernor {
211+
if (address(issuanceAllocator) != newIssuanceAllocator) {
212+
// Update rewards calculation before changing the issuance allocator
213+
updateAccRewardsPerSignal();
214+
215+
// Check that the contract supports the IIssuanceAllocationDistribution interface
216+
// Allow zero address to disable the allocator
217+
if (newIssuanceAllocator != address(0)) {
218+
require(
219+
IERC165(newIssuanceAllocator).supportsInterface(type(IIssuanceAllocationDistribution).interfaceId),
220+
"Contract does not support IIssuanceAllocationDistribution interface"
221+
);
222+
}
223+
224+
address oldIssuanceAllocator = address(issuanceAllocator);
225+
issuanceAllocator = IIssuanceAllocationDistribution(newIssuanceAllocator);
226+
emit IssuanceAllocatorSet(oldIssuanceAllocator, newIssuanceAllocator);
227+
}
228+
}
229+
230+
/**
231+
* @inheritdoc IIssuanceTarget
232+
* @dev Ensures that all reward calculations are up-to-date with the current block
233+
* before any allocation changes take effect.
234+
*
235+
* This function can be called by anyone to update the rewards calculation state.
236+
* The IssuanceAllocator calls this function before changing a target's allocation to ensure
237+
* all issuance is properly accounted for with the current issuance rate before applying an
238+
* issuance allocation change.
239+
*/
240+
function beforeIssuanceAllocationChange() external override {
241+
// Update rewards calculation with the current issuance rate
242+
updateAccRewardsPerSignal();
243+
}
244+
245+
/**
246+
* @inheritdoc IRewardsManager
247+
* @dev Note that the rewards eligibility oracle can be set to the zero address to disable use of an oracle, in
248+
* which case no indexers will be denied rewards due to eligibility.
249+
*/
250+
function setRewardsEligibilityOracle(address newRewardsEligibilityOracle) external override onlyGovernor {
251+
if (address(rewardsEligibilityOracle) != newRewardsEligibilityOracle) {
252+
// Check that the contract supports the IRewardsEligibility interface
253+
// Allow zero address to disable the oracle
254+
if (newRewardsEligibilityOracle != address(0)) {
255+
require(
256+
IERC165(newRewardsEligibilityOracle).supportsInterface(type(IRewardsEligibility).interfaceId),
257+
"Contract does not support IRewardsEligibility interface"
258+
);
259+
}
260+
261+
address oldRewardsEligibilityOracle = address(rewardsEligibilityOracle);
262+
rewardsEligibilityOracle = IRewardsEligibility(newRewardsEligibilityOracle);
263+
emit RewardsEligibilityOracleSet(oldRewardsEligibilityOracle, newRewardsEligibilityOracle);
264+
}
265+
}
266+
155267
// -- Denylist --
156268

157269
/**
@@ -180,6 +292,17 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
180292

181293
// -- Getters --
182294

295+
/**
296+
* @inheritdoc IRewardsManager
297+
* @dev Gets the effective issuance per block, taking into account the IssuanceAllocator if set
298+
*/
299+
function getRewardsIssuancePerBlock() public view override returns (uint256) {
300+
if (address(issuanceAllocator) != address(0)) {
301+
return issuanceAllocator.getTargetIssuancePerBlock(address(this)).selfIssuancePerBlock;
302+
}
303+
return issuancePerBlock;
304+
}
305+
183306
/**
184307
* @inheritdoc IRewardsManager
185308
* @dev Linear formula: `x = r * t`
@@ -197,8 +320,10 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
197320
if (t == 0) {
198321
return 0;
199322
}
200-
// ...or if issuance is zero
201-
if (issuancePerBlock == 0) {
323+
324+
uint256 rewardsIssuancePerBlock = getRewardsIssuancePerBlock();
325+
326+
if (rewardsIssuancePerBlock == 0) {
202327
return 0;
203328
}
204329

@@ -209,7 +334,7 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
209334
return 0;
210335
}
211336

212-
uint256 x = issuancePerBlock.mul(t);
337+
uint256 x = rewardsIssuancePerBlock.mul(t);
213338

214339
// Get the new issuance per signalled token
215340
// We multiply the decimals to keep the precision as fixed-point number
@@ -405,6 +530,13 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
405530
rewards = accRewardsPending.add(
406531
_calcRewards(tokens, accRewardsPerAllocatedToken, updatedAccRewardsPerAllocatedToken)
407532
);
533+
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+
}
539+
408540
if (rewards > 0) {
409541
// Mint directly to rewards issuer for the reward amount
410542
// The rewards issuer contract will do bookkeeping of the reward and

packages/contracts/contracts/rewards/RewardsManagerStorage.sol

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
pragma solidity ^0.7.6 || 0.8.27;
99

10+
import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol";
11+
import { IRewardsEligibility } from "@graphprotocol/interfaces/contracts/issuance/eligibility/IRewardsEligibility.sol";
1012
import { IRewardsIssuer } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol";
1113
import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol";
1214
import { Managed } from "../governance/Managed.sol";
@@ -76,3 +78,16 @@ contract RewardsManagerV5Storage is RewardsManagerV4Storage {
7678
/// @notice Address of the subgraph service
7779
IRewardsIssuer public subgraphService;
7880
}
81+
82+
/**
83+
* @title RewardsManagerV6Storage
84+
* @author Edge & Node
85+
* @notice Storage layout for RewardsManager V6
86+
* Includes support for Rewards Eligibility Oracle and Issuance Allocator.
87+
*/
88+
contract RewardsManagerV6Storage is RewardsManagerV5Storage {
89+
/// @notice Address of the rewards eligibility oracle contract
90+
IRewardsEligibility public rewardsEligibilityOracle;
91+
/// @notice Address of the issuance allocator
92+
IIssuanceAllocationDistribution public issuanceAllocator;
93+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
pragma solidity 0.7.6;
4+
5+
import { IERC165 } from "@openzeppelin/contracts/introspection/IERC165.sol";
6+
7+
/**
8+
* @title MockERC165
9+
* @author Edge & Node
10+
* @dev Minimal implementation of IERC165 for testing
11+
* @notice Used to test interface validation - supports only ERC165, not specific interfaces
12+
*/
13+
contract MockERC165 is IERC165 {
14+
/**
15+
* @inheritdoc IERC165
16+
*/
17+
function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
18+
return interfaceId == type(IERC165).interfaceId;
19+
}
20+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
// solhint-disable gas-increment-by-one, gas-indexed-events, named-parameters-mapping, use-natspec
4+
5+
pragma solidity 0.7.6;
6+
pragma abicoder v2;
7+
8+
import { IERC165 } from "@openzeppelin/contracts/introspection/IERC165.sol";
9+
import { TargetIssuancePerBlock } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocatorTypes.sol";
10+
import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol";
11+
import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol";
12+
13+
/**
14+
* @title MockIssuanceAllocator
15+
* @dev A simple mock contract for the IssuanceAllocator interfaces used by RewardsManager.
16+
*/
17+
contract MockIssuanceAllocator is IERC165, IIssuanceAllocationDistribution {
18+
/// @dev Mapping to store TargetIssuancePerBlock for each target
19+
mapping(address => TargetIssuancePerBlock) private _targetIssuance;
20+
21+
/**
22+
* @dev Call beforeIssuanceAllocationChange on a target
23+
* @param target The target contract address
24+
*/
25+
function callBeforeIssuanceAllocationChange(address target) external {
26+
IIssuanceTarget(target).beforeIssuanceAllocationChange();
27+
}
28+
29+
/**
30+
* @inheritdoc IIssuanceAllocationDistribution
31+
*/
32+
function getTargetIssuancePerBlock(address target) external view override returns (TargetIssuancePerBlock memory) {
33+
return _targetIssuance[target];
34+
}
35+
36+
/**
37+
* @inheritdoc IIssuanceAllocationDistribution
38+
* @dev Mock always returns current block number
39+
*/
40+
function distributeIssuance() external view override returns (uint256) {
41+
return block.number;
42+
}
43+
44+
/**
45+
* @dev Set target issuance directly for testing
46+
* @param target The target contract address
47+
* @param allocatorIssuance The allocator issuance per block
48+
* @param selfIssuance The self issuance per block
49+
* @param callBefore Whether to call beforeIssuanceAllocationChange on the target
50+
*/
51+
function setTargetAllocation(
52+
address target,
53+
uint256 allocatorIssuance,
54+
uint256 selfIssuance,
55+
bool callBefore
56+
) external {
57+
if (callBefore) {
58+
IIssuanceTarget(target).beforeIssuanceAllocationChange();
59+
}
60+
_targetIssuance[target] = TargetIssuancePerBlock({
61+
allocatorIssuancePerBlock: allocatorIssuance,
62+
allocatorIssuanceBlockAppliedTo: block.number,
63+
selfIssuancePerBlock: selfIssuance,
64+
selfIssuanceBlockAppliedTo: block.number
65+
});
66+
}
67+
68+
/**
69+
* @inheritdoc IERC165
70+
*/
71+
function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
72+
return
73+
interfaceId == type(IIssuanceAllocationDistribution).interfaceId ||
74+
interfaceId == type(IERC165).interfaceId;
75+
}
76+
}

0 commit comments

Comments
 (0)