diff --git a/audits/OpenZeppelin/2021-12-graph-rewards-signal-threshold.pdf b/audits/OpenZeppelin/2021-12-graph-rewards-signal-threshold.pdf new file mode 100644 index 000000000..0c1add753 Binary files /dev/null and b/audits/OpenZeppelin/2021-12-graph-rewards-signal-threshold.pdf differ diff --git a/contracts/rewards/IRewardsManager.sol b/contracts/rewards/IRewardsManager.sol index 5f044be5b..dc17c8ba8 100644 --- a/contracts/rewards/IRewardsManager.sol +++ b/contracts/rewards/IRewardsManager.sol @@ -13,10 +13,12 @@ interface IRewardsManager { uint256 accRewardsPerAllocatedToken; } - // -- Params -- + // -- Config -- function setIssuanceRate(uint256 _issuanceRate) external; + function setMinimumSubgraphSignal(uint256 _minimumSubgraphSignal) external; + // -- Denylist -- function setSubgraphAvailabilityOracle(address _subgraphAvailabilityOracle) external; diff --git a/contracts/rewards/RewardsManager.sol b/contracts/rewards/RewardsManager.sol index 4c3903bdd..9a0e24d2a 100644 --- a/contracts/rewards/RewardsManager.sol +++ b/contracts/rewards/RewardsManager.sol @@ -17,8 +17,17 @@ import "./IRewardsManager.sol"; * towards each subgraph. Then each Subgraph can have multiple Indexers Staked on it. Thus, the * total rewards for the Subgraph are split up for each Indexer based on much they have Staked on * that Subgraph. + * + * Note: + * The contract provides getter functions to query the state of accrued rewards: + * - getAccRewardsPerSignal + * - getAccRewardsForSubgraph + * - getAccRewardsPerAllocatedToken + * - getRewards + * These functions may overestimate the actual rewards due to changes in the total supply + * until the actual takeRewards function is called. */ -contract RewardsManager is RewardsManagerV1Storage, GraphUpgradeable, IRewardsManager { +contract RewardsManager is RewardsManagerV2Storage, GraphUpgradeable, IRewardsManager { using SafeMath for uint256; uint256 private constant TOKEN_DECIMALS = 1e18; @@ -66,6 +75,8 @@ contract RewardsManager is RewardsManagerV1Storage, GraphUpgradeable, IRewardsMa _setIssuanceRate(_issuanceRate); } + // -- Config -- + /** * @dev Sets the issuance rate. * The issuance rate is defined as a percentage increase of the total supply per block. @@ -105,6 +116,24 @@ contract RewardsManager is RewardsManagerV1Storage, GraphUpgradeable, IRewardsMa emit ParameterUpdated("subgraphAvailabilityOracle"); } + /** + * @dev Sets the minimum signaled tokens on a subgraph to start accruing rewards. + * @dev Can be set to zero which means that this feature is not being used. + * @param _minimumSubgraphSignal Minimum signaled tokens + */ + function setMinimumSubgraphSignal(uint256 _minimumSubgraphSignal) external override { + // Caller can be the SAO or the governor + require( + msg.sender == address(subgraphAvailabilityOracle) || + msg.sender == controller.getGovernor(), + "Not authorized" + ); + minimumSubgraphSignal = _minimumSubgraphSignal; + emit ParameterUpdated("minimumSubgraphSignal"); + } + + // -- Denylist -- + /** * @dev Denies to claim rewards for a subgraph. * NOTE: Can only be called by the subgraph availability oracle @@ -150,11 +179,14 @@ contract RewardsManager is RewardsManagerV1Storage, GraphUpgradeable, IRewardsMa /** * @dev Tells if subgraph is in deny list * @param _subgraphDeploymentID Subgraph deployment ID to check + * @return Whether the subgraph is denied for claiming rewards or not */ function isDenied(bytes32 _subgraphDeploymentID) public view override returns (bool) { return denylist[_subgraphDeploymentID] > 0; } + // -- Getters -- + /** * @dev Gets the issuance of rewards per signal since last updated. * @@ -205,6 +237,7 @@ contract RewardsManager is RewardsManagerV1Storage, GraphUpgradeable, IRewardsMa /** * @dev Gets the currently accumulated rewards per signal. + * @return Currently accumulated rewards per signal */ function getAccRewardsPerSignal() public view override returns (uint256) { return accRewardsPerSignal.add(getNewRewardsPerSignal()); @@ -223,11 +256,16 @@ contract RewardsManager is RewardsManagerV1Storage, GraphUpgradeable, IRewardsMa { Subgraph storage subgraph = subgraphs[_subgraphDeploymentID]; - uint256 newRewardsPerSignal = getAccRewardsPerSignal().sub( - subgraph.accRewardsPerSignalSnapshot - ); + // Get tokens signalled on the subgraph uint256 subgraphSignalledTokens = curation().getCurationPoolTokens(_subgraphDeploymentID); - uint256 newRewards = newRewardsPerSignal.mul(subgraphSignalledTokens).div(TOKEN_DECIMALS); + + // Only accrue rewards if over a threshold + uint256 newRewards = (subgraphSignalledTokens >= minimumSubgraphSignal) // Accrue new rewards since last snapshot + ? getAccRewardsPerSignal() + .sub(subgraph.accRewardsPerSignalSnapshot) + .mul(subgraphSignalledTokens) + .div(TOKEN_DECIMALS) + : 0; return subgraph.accRewardsForSubgraph.add(newRewards); } @@ -266,6 +304,8 @@ contract RewardsManager is RewardsManagerV1Storage, GraphUpgradeable, IRewardsMa ); } + // -- Updates -- + /** * @dev Updates the accumulated rewards per signal and save checkpoint block number. * Must be called before `issuanceRate` or `total signalled GRT` changes diff --git a/contracts/rewards/RewardsManagerStorage.sol b/contracts/rewards/RewardsManagerStorage.sol index ca7846671..2bb0c2978 100644 --- a/contracts/rewards/RewardsManagerStorage.sol +++ b/contracts/rewards/RewardsManagerStorage.sol @@ -21,3 +21,8 @@ contract RewardsManagerV1Storage is Managed { // Subgraph denylist : subgraph deployment ID => block when added or zero (if not denied) mapping(bytes32 => uint256) public denylist; } + +contract RewardsManagerV2Storage is RewardsManagerV1Storage { + // Minimum amount of signaled tokens on a subgraph required to accrue rewards + uint256 public minimumSubgraphSignal; +}