Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 138 additions & 6 deletions packages/contracts/contracts/rewards/RewardsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@
// TODO: Re-enable and fix issues when publishing a new version
// solhint-disable gas-increment-by-one, gas-indexed-events, gas-small-strings, gas-strict-inequalities

import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";

Check warning on line 9 in packages/contracts/contracts/rewards/RewardsManager.sol

View workflow job for this annotation

GitHub Actions / Lint Files

Import in packages/contracts/contracts/rewards/RewardsManager.sol doesn't exist in: @openzeppelin/contracts/math/SafeMath.sol
import { IERC165 } from "@openzeppelin/contracts/introspection/IERC165.sol";

Check warning on line 10 in packages/contracts/contracts/rewards/RewardsManager.sol

View workflow job for this annotation

GitHub Actions / Lint Files

Import in packages/contracts/contracts/rewards/RewardsManager.sol doesn't exist in: @openzeppelin/contracts/introspection/IERC165.sol

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

Check warning on line 15 in packages/contracts/contracts/rewards/RewardsManager.sol

View workflow job for this annotation

GitHub Actions / Lint Files

Import in packages/contracts/contracts/rewards/RewardsManager.sol doesn't exist in: @graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol

import { RewardsManagerV5Storage } from "./RewardsManagerStorage.sol";
import { RewardsManagerV6Storage } from "./RewardsManagerStorage.sol";
import { IRewardsIssuer } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol";

Check warning on line 18 in packages/contracts/contracts/rewards/RewardsManager.sol

View workflow job for this annotation

GitHub Actions / Lint Files

Import in packages/contracts/contracts/rewards/RewardsManager.sol doesn't exist in: @graphprotocol/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol
import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol";

Check warning on line 19 in packages/contracts/contracts/rewards/RewardsManager.sol

View workflow job for this annotation

GitHub Actions / Lint Files

Import in packages/contracts/contracts/rewards/RewardsManager.sol doesn't exist in: @graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol
import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol";

Check warning on line 20 in packages/contracts/contracts/rewards/RewardsManager.sol

View workflow job for this annotation

GitHub Actions / Lint Files

Import in packages/contracts/contracts/rewards/RewardsManager.sol doesn't exist in: @graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol
import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol";

Check warning on line 21 in packages/contracts/contracts/rewards/RewardsManager.sol

View workflow job for this annotation

GitHub Actions / Lint Files

Import in packages/contracts/contracts/rewards/RewardsManager.sol doesn't exist in: @graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol
import { IRewardsEligibility } from "@graphprotocol/interfaces/contracts/issuance/eligibility/IRewardsEligibility.sol";

Check warning on line 22 in packages/contracts/contracts/rewards/RewardsManager.sol

View workflow job for this annotation

GitHub Actions / Lint Files

Import in packages/contracts/contracts/rewards/RewardsManager.sol doesn't exist in: @graphprotocol/interfaces/contracts/issuance/eligibility/IRewardsEligibility.sol

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

/// @dev Fixed point scaling factor used for decimals in reward calculations
Expand All @@ -61,6 +69,14 @@
*/
event RewardsDenied(address indexed indexer, address indexed allocationID);

/**
* @notice Emitted when rewards are denied to an indexer due to eligibility
* @param indexer Address of the indexer being denied rewards
* @param allocationID Address of the allocation being denied rewards
* @param amount Amount of rewards that would have been assigned
*/
event RewardsDeniedDueToEligibility(address indexed indexer, address indexed allocationID, uint256 amount);

/**
* @notice Emitted when a subgraph is denied for claiming rewards
* @param subgraphDeploymentID Subgraph deployment ID being denied
Expand All @@ -75,6 +91,23 @@
*/
event SubgraphServiceSet(address indexed oldSubgraphService, address indexed newSubgraphService);

/**
* @notice Emitted when the issuance allocator is set
* @param oldIssuanceAllocator Previous issuance allocator address
* @param newIssuanceAllocator New issuance allocator address
*/
event IssuanceAllocatorSet(address indexed oldIssuanceAllocator, address indexed newIssuanceAllocator);

/**
* @notice Emitted when the rewards eligibility oracle contract is set
* @param oldRewardsEligibilityOracle Previous rewards eligibility oracle address
* @param newRewardsEligibilityOracle New rewards eligibility oracle address
*/
event RewardsEligibilityOracleSet(
address indexed oldRewardsEligibilityOracle,
address indexed newRewardsEligibilityOracle
);

// -- Modifiers --

/**
Expand All @@ -93,12 +126,27 @@
Managed._initialize(_controller);
}

/**
* @inheritdoc IERC165
* @dev Implements ERC165 interface detection
* Returns true if this contract implements the interface defined by interfaceId.
* See: https://eips.ethereum.org/EIPS/eip-165
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return
interfaceId == type(IERC165).interfaceId ||
interfaceId == type(IIssuanceTarget).interfaceId ||
interfaceId == type(IRewardsManager).interfaceId;
}

// -- Config --

/**
* @inheritdoc IRewardsManager
* @dev When an IssuanceAllocator is set, the effective issuance will be determined by the allocator,
* but this local value can still be updated for cases when the allocator is later removed.
*
* @dev The issuance is defined as a fixed amount of rewards per block in GRT.
* The issuance is defined as a fixed amount of rewards per block in GRT.
* Whenever this function is called in layer 2, the updateL2MintAllowance function
* _must_ be called on the L1GraphTokenGateway in L1, to ensure the bridge can mint the
* right amount of tokens.
Expand Down Expand Up @@ -152,6 +200,70 @@
emit SubgraphServiceSet(oldSubgraphService, _subgraphService);
}

/**
* @inheritdoc IIssuanceTarget
* @dev This function facilitates upgrades by providing a standard way for targets
* to change their allocator. Only the governor can call this function.
* Note that the IssuanceAllocator can be set to the zero address to disable use of an allocator, and
* use the local `issuancePerBlock` variable instead to control issuance.
*/
function setIssuanceAllocator(address newIssuanceAllocator) external override onlyGovernor {
if (address(issuanceAllocator) != newIssuanceAllocator) {
// Update rewards calculation before changing the issuance allocator
updateAccRewardsPerSignal();

// Check that the contract supports the IIssuanceAllocationDistribution interface
// Allow zero address to disable the allocator
if (newIssuanceAllocator != address(0)) {
require(
IERC165(newIssuanceAllocator).supportsInterface(type(IIssuanceAllocationDistribution).interfaceId),
"Contract does not support IIssuanceAllocationDistribution interface"
);
}

address oldIssuanceAllocator = address(issuanceAllocator);
issuanceAllocator = IIssuanceAllocationDistribution(newIssuanceAllocator);
emit IssuanceAllocatorSet(oldIssuanceAllocator, newIssuanceAllocator);
}
}

/**
* @inheritdoc IIssuanceTarget
* @dev Ensures that all reward calculations are up-to-date with the current block
* before any allocation changes take effect.
*
* This function can be called by anyone to update the rewards calculation state.
* The IssuanceAllocator calls this function before changing a target's allocation to ensure
* all issuance is properly accounted for with the current issuance rate before applying an
* issuance allocation change.
*/
function beforeIssuanceAllocationChange() external override {
// Update rewards calculation with the current issuance rate
updateAccRewardsPerSignal();
}

/**
* @inheritdoc IRewardsManager
* @dev Note that the rewards eligibility oracle can be set to the zero address to disable use of an oracle, in
* which case no indexers will be denied rewards due to eligibility.
*/
function setRewardsEligibilityOracle(address newRewardsEligibilityOracle) external override onlyGovernor {
if (address(rewardsEligibilityOracle) != newRewardsEligibilityOracle) {
// Check that the contract supports the IRewardsEligibility interface
// Allow zero address to disable the oracle
if (newRewardsEligibilityOracle != address(0)) {
require(
IERC165(newRewardsEligibilityOracle).supportsInterface(type(IRewardsEligibility).interfaceId),
"Contract does not support IRewardsEligibility interface"
);
}

address oldRewardsEligibilityOracle = address(rewardsEligibilityOracle);
rewardsEligibilityOracle = IRewardsEligibility(newRewardsEligibilityOracle);
emit RewardsEligibilityOracleSet(oldRewardsEligibilityOracle, newRewardsEligibilityOracle);
}
}

// -- Denylist --

/**
Expand Down Expand Up @@ -180,6 +292,17 @@

// -- Getters --

/**
* @inheritdoc IRewardsManager
* @dev Gets the effective issuance per block, taking into account the IssuanceAllocator if set
*/
function getRewardsIssuancePerBlock() public view override returns (uint256) {
if (address(issuanceAllocator) != address(0)) {
return issuanceAllocator.getTargetIssuancePerBlock(address(this)).selfIssuancePerBlock;
}
return issuancePerBlock;
}

/**
* @inheritdoc IRewardsManager
* @dev Linear formula: `x = r * t`
Expand All @@ -197,8 +320,10 @@
if (t == 0) {
return 0;
}
// ...or if issuance is zero
if (issuancePerBlock == 0) {

uint256 rewardsIssuancePerBlock = getRewardsIssuancePerBlock();

if (rewardsIssuancePerBlock == 0) {
return 0;
}

Expand All @@ -209,7 +334,7 @@
return 0;
}

uint256 x = issuancePerBlock.mul(t);
uint256 x = rewardsIssuancePerBlock.mul(t);

// Get the new issuance per signalled token
// We multiply the decimals to keep the precision as fixed-point number
Expand Down Expand Up @@ -405,6 +530,13 @@
rewards = accRewardsPending.add(
_calcRewards(tokens, accRewardsPerAllocatedToken, updatedAccRewardsPerAllocatedToken)
);

// Do not reward if indexer is not eligible based on rewards eligibility
if (address(rewardsEligibilityOracle) != address(0) && !rewardsEligibilityOracle.isEligible(indexer)) {
emit RewardsDeniedDueToEligibility(indexer, _allocationID, rewards);
return 0;
}

if (rewards > 0) {
// Mint directly to rewards issuer for the reward amount
// The rewards issuer contract will do bookkeeping of the reward and
Expand Down
15 changes: 15 additions & 0 deletions packages/contracts/contracts/rewards/RewardsManagerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

pragma solidity ^0.7.6 || 0.8.27;

import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol";

Check warning on line 10 in packages/contracts/contracts/rewards/RewardsManagerStorage.sol

View workflow job for this annotation

GitHub Actions / Lint Files

Import in packages/contracts/contracts/rewards/RewardsManagerStorage.sol doesn't exist in: @graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol
import { IRewardsEligibility } from "@graphprotocol/interfaces/contracts/issuance/eligibility/IRewardsEligibility.sol";

Check warning on line 11 in packages/contracts/contracts/rewards/RewardsManagerStorage.sol

View workflow job for this annotation

GitHub Actions / Lint Files

Import in packages/contracts/contracts/rewards/RewardsManagerStorage.sol doesn't exist in: @graphprotocol/interfaces/contracts/issuance/eligibility/IRewardsEligibility.sol
import { IRewardsIssuer } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol";
import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol";
import { Managed } from "../governance/Managed.sol";
Expand Down Expand Up @@ -76,3 +78,16 @@
/// @notice Address of the subgraph service
IRewardsIssuer public subgraphService;
}

/**
* @title RewardsManagerV6Storage
* @author Edge & Node
* @notice Storage layout for RewardsManager V6
* Includes support for Rewards Eligibility Oracle and Issuance Allocator.
*/
contract RewardsManagerV6Storage is RewardsManagerV5Storage {
/// @notice Address of the rewards eligibility oracle contract
IRewardsEligibility public rewardsEligibilityOracle;
/// @notice Address of the issuance allocator
IIssuanceAllocationDistribution public issuanceAllocator;
}
20 changes: 20 additions & 0 deletions packages/contracts/contracts/tests/MockERC165.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity 0.7.6;

import { IERC165 } from "@openzeppelin/contracts/introspection/IERC165.sol";

/**
* @title MockERC165
* @author Edge & Node
* @dev Minimal implementation of IERC165 for testing
* @notice Used to test interface validation - supports only ERC165, not specific interfaces
*/
contract MockERC165 is IERC165 {
/**
* @inheritdoc IERC165
*/
function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
76 changes: 76 additions & 0 deletions packages/contracts/contracts/tests/MockIssuanceAllocator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: GPL-2.0-or-later

// solhint-disable gas-increment-by-one, gas-indexed-events, named-parameters-mapping, use-natspec

pragma solidity 0.7.6;
pragma abicoder v2;

import { IERC165 } from "@openzeppelin/contracts/introspection/IERC165.sol";
import { TargetIssuancePerBlock } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocatorTypes.sol";
import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol";
import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol";

/**
* @title MockIssuanceAllocator
* @dev A simple mock contract for the IssuanceAllocator interfaces used by RewardsManager.
*/
contract MockIssuanceAllocator is IERC165, IIssuanceAllocationDistribution {
/// @dev Mapping to store TargetIssuancePerBlock for each target
mapping(address => TargetIssuancePerBlock) private _targetIssuance;

/**
* @dev Call beforeIssuanceAllocationChange on a target
* @param target The target contract address
*/
function callBeforeIssuanceAllocationChange(address target) external {
IIssuanceTarget(target).beforeIssuanceAllocationChange();
}

/**
* @inheritdoc IIssuanceAllocationDistribution
*/
function getTargetIssuancePerBlock(address target) external view override returns (TargetIssuancePerBlock memory) {
return _targetIssuance[target];
}

/**
* @inheritdoc IIssuanceAllocationDistribution
* @dev Mock always returns current block number
*/
function distributeIssuance() external view override returns (uint256) {
return block.number;
}

/**
* @dev Set target issuance directly for testing
* @param target The target contract address
* @param allocatorIssuance The allocator issuance per block
* @param selfIssuance The self issuance per block
* @param callBefore Whether to call beforeIssuanceAllocationChange on the target
*/
function setTargetAllocation(
address target,
uint256 allocatorIssuance,
uint256 selfIssuance,
bool callBefore
) external {
if (callBefore) {
IIssuanceTarget(target).beforeIssuanceAllocationChange();
}
_targetIssuance[target] = TargetIssuancePerBlock({
allocatorIssuancePerBlock: allocatorIssuance,
allocatorIssuanceBlockAppliedTo: block.number,
selfIssuancePerBlock: selfIssuance,
selfIssuanceBlockAppliedTo: block.number
});
}

/**
* @inheritdoc IERC165
*/
function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
return
interfaceId == type(IIssuanceAllocationDistribution).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}
}
Loading
Loading