Skip to content

Service Quality Oracle #1214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: baseline-upgrade
Choose a base branch
from
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
17 changes: 17 additions & 0 deletions packages/common/contracts/quality/IServiceQualityOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.7.6 || ^0.8.0;

/**
* @title IServiceQualityOracle
* @author Edge & Node
* @notice Interface to check if an indexer is allowed to receive rewards based on its service quality
*/
interface IServiceQualityOracle {
/**
* @notice Check if an indexer is allowed to receive rewards
* @param indexer Address of the indexer
* @return True if the indexer is allowed to receive rewards, false otherwise
*/
function isAllowed(address indexer) external view returns (bool);
}
6 changes: 6 additions & 0 deletions packages/common/contracts/rewards/IRewardsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ interface IRewardsManager {
*/
function setMinimumSubgraphSignal(uint256 _minimumSubgraphSignal) external;

/**
* @notice Set the service quality oracle address
* @param newServiceQualityOracle The address of the service quality oracle
*/
function setServiceQualityOracle(address newServiceQualityOracle) external;

// -- Denylist --

/**
Expand Down
42 changes: 42 additions & 0 deletions packages/contracts/contracts/rewards/IRewardsIssuer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.7.6 || 0.8.27;

/**
* @title Rewards Issuer Interface
* @author Edge & Node
* @notice Interface for contracts that issue rewards based on allocation data
*/
interface IRewardsIssuer {
/**
* @notice Get allocation data to calculate rewards issuance
*
* @param allocationId The allocation Id
* @return isActive Whether the allocation is active or not
* @return indexer The indexer address
* @return subgraphDeploymentId Subgraph deployment id for the allocation
* @return tokens Amount of allocated tokens
* @return accRewardsPerAllocatedToken Rewards snapshot
* @return accRewardsPending Snapshot of accumulated rewards from previous allocation resizing, pending to be claimed
*/
function getAllocationData(
address allocationId
)
external
view
returns (
bool isActive,
address indexer,
bytes32 subgraphDeploymentId,
uint256 tokens,
uint256 accRewardsPerAllocatedToken,
uint256 accRewardsPending
);

/**
* @notice Return the total amount of tokens allocated to subgraph.
* @param _subgraphDeploymentId Deployment Id for the subgraph
* @return Total tokens allocated to subgraph
*/
function getSubgraphAllocatedTokens(bytes32 _subgraphDeploymentId) external view returns (uint256);
}
50 changes: 48 additions & 2 deletions packages/contracts/contracts/rewards/RewardsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@
// 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 { MathUtils } from "../staking/libs/MathUtils.sol";
import { Managed } from "../governance/Managed.sol";
import { IGraphToken } from "@graphprotocol/common/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/common/contracts/token/IGraphToken.sol
import { IStaking, IStakingBase } from "../staking/IStaking.sol";

import { RewardsManagerV4Storage } from "./RewardsManagerStorage.sol";
import { RewardsManagerV5Storage } from "./RewardsManagerStorage.sol";
import { IRewardsManager } from "@graphprotocol/common/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/common/contracts/rewards/IRewardsManager.sol
import { IServiceQualityOracle } from "@graphprotocol/common/contracts/quality/IServiceQualityOracle.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/common/contracts/quality/IServiceQualityOracle.sol

/**
* @title Rewards Manager Contract
Expand All @@ -37,7 +39,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 RewardsManagerV4Storage, GraphUpgradeable, IRewardsManager {
contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsManager {
using SafeMath for uint256;

/// @dev Fixed point scaling factor used for decimals in reward calculations
Expand All @@ -62,13 +64,28 @@
*/
event RewardsDenied(address indexed indexer, address indexed allocationID, uint256 epoch);

/**
* @notice Emitted when rewards are denied to an indexer due to service quality
* @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 RewardsDeniedDueToServiceQuality(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
* @param sinceBlock Block number since when the subgraph is denied
*/
event RewardsDenylistUpdated(bytes32 indexed subgraphDeploymentID, uint256 sinceBlock);

/**
* @notice Emitted when the service quality oracle contract is set
* @param oldServiceQualityOracle Previous service quality oracle address
* @param newServiceQualityOracle New service quality oracle address
*/
event ServiceQualityOracleSet(address indexed oldServiceQualityOracle, address indexed newServiceQualityOracle);

// -- Modifiers --

/**
Expand Down Expand Up @@ -135,6 +152,28 @@
emit ParameterUpdated("minimumSubgraphSignal");
}

/**
* @inheritdoc IRewardsManager
* @dev Note that the service quality 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 service quality.
*/
function setServiceQualityOracle(address newServiceQualityOracle) external override onlyGovernor {
if (address(serviceQualityOracle) != newServiceQualityOracle) {
// Check that the contract supports the IServiceQualityOracle interface
// Allow zero address to disable the oracle
if (newServiceQualityOracle != address(0)) {
require(
IERC165(newServiceQualityOracle).supportsInterface(type(IServiceQualityOracle).interfaceId),
"Contract does not support IServiceQualityOracle interface"
);
}

address oldServiceQualityOracle = address(serviceQualityOracle);
serviceQualityOracle = IServiceQualityOracle(newServiceQualityOracle);
emit ServiceQualityOracleSet(oldServiceQualityOracle, newServiceQualityOracle);
}
}

// -- Denylist --

/**
Expand Down Expand Up @@ -336,6 +375,13 @@

// Calculate rewards accrued by this allocation
uint256 rewards = _calcRewards(alloc.tokens, alloc.accRewardsPerAllocatedToken, accRewardsPerAllocatedToken);

// Do not reward if indexer is not eligible based on service quality
if (address(serviceQualityOracle) != address(0) && !serviceQualityOracle.isAllowed(alloc.indexer)) {
emit RewardsDeniedDueToServiceQuality(alloc.indexer, _allocationID, rewards);
return 0;
}

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

pragma solidity 0.7.6;

import { IRewardsManager } from "@graphprotocol/common/contracts/rewards/IRewardsManager.sol";

Check warning on line 7 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/common/contracts/rewards/IRewardsManager.sol
import { IRewardsIssuer } from "./IRewardsIssuer.sol";
import { IServiceQualityOracle } from "@graphprotocol/common/contracts/quality/IServiceQualityOracle.sol";

Check warning on line 9 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/common/contracts/quality/IServiceQualityOracle.sol
import { Managed } from "../governance/Managed.sol";

/**
Expand Down Expand Up @@ -59,6 +61,17 @@
*/
contract RewardsManagerV4Storage is RewardsManagerV3Storage {
/// @notice GRT issued for indexer rewards per block
/// @dev Only used when issuanceAllocator is zero address.
uint256 public issuancePerBlock;
}

/**
* @title RewardsManagerV5Storage
* @author Edge & Node
* @notice Storage layout for RewardsManager V5
*/
contract RewardsManagerV5Storage is RewardsManagerV4Storage {
/// @notice Address of the subgraph service
IRewardsIssuer public subgraphService;
/// @notice Address of the service quality oracle contract
IServiceQualityOracle public serviceQualityOracle;
}
21 changes: 21 additions & 0 deletions packages/contracts/contracts/tests/MockERC165OnlyContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity 0.7.6;

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

Check warning on line 5 in packages/contracts/contracts/tests/MockERC165OnlyContract.sol

View workflow job for this annotation

GitHub Actions / Lint Files

Import in packages/contracts/contracts/tests/MockERC165OnlyContract.sol doesn't exist in: @openzeppelin/contracts/introspection/ERC165.sol

/**
* @title MockERC165OnlyContract
* @author Edge & Node
* @notice A mock contract that supports ERC-165 but not IServiceQualityOracle
* @dev Used for testing ERC-165 interface checking in RewardsManager
*/
contract MockERC165OnlyContract is ERC165 {
/**
* @notice A dummy function to make this a non-trivial contract
* @return A dummy string
*/
function dummyFunction() external pure returns (string memory) {
return "This contract only supports ERC-165";
}
}
68 changes: 68 additions & 0 deletions packages/contracts/contracts/tests/MockServiceQualityOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity 0.7.6;

import { IServiceQualityOracle } from "@graphprotocol/common/contracts/quality/IServiceQualityOracle.sol";

Check warning on line 5 in packages/contracts/contracts/tests/MockServiceQualityOracle.sol

View workflow job for this annotation

GitHub Actions / Lint Files

Import in packages/contracts/contracts/tests/MockServiceQualityOracle.sol doesn't exist in: @graphprotocol/common/contracts/quality/IServiceQualityOracle.sol
import { ERC165 } from "@openzeppelin/contracts/introspection/ERC165.sol";

Check warning on line 6 in packages/contracts/contracts/tests/MockServiceQualityOracle.sol

View workflow job for this annotation

GitHub Actions / Lint Files

Import in packages/contracts/contracts/tests/MockServiceQualityOracle.sol doesn't exist in: @openzeppelin/contracts/introspection/ERC165.sol

/**
* @title MockServiceQualityOracle
* @author Edge & Node
* @notice A simple mock contract for the ServiceQualityOracle interface
* @dev A simple mock contract for the ServiceQualityOracle interface
*/
contract MockServiceQualityOracle is IServiceQualityOracle, ERC165 {
/// @dev Mapping to store allowed status for each indexer
mapping(address => bool) private _allowed;

/// @dev Mapping to track which indexers have been explicitly set
mapping(address => bool) private _isSet;

/// @dev Default response for indexers not explicitly set
bool private _defaultResponse;

/**
* @notice Constructor
* @param defaultResponse Default response for isAllowed
*/
constructor(bool defaultResponse) {
_defaultResponse = defaultResponse;
}

/**
* @notice Set whether a specific indexer is allowed
* @param indexer The indexer address
* @param allowed Whether the indexer is allowed
*/
function setIndexerAllowed(address indexer, bool allowed) external {
_allowed[indexer] = allowed;
}

/**
* @notice Set the default response for indexers not explicitly set
* @param defaultResponse The default response
*/
function setDefaultResponse(bool defaultResponse) external {
_defaultResponse = defaultResponse;
}

/**
* @inheritdoc IServiceQualityOracle
*/
function isAllowed(address indexer) external view override returns (bool) {
// If the indexer has been explicitly set, return that value
if (_isSet[indexer]) {
return _allowed[indexer];
}

// Otherwise return the default response
return _defaultResponse;
}

/**
* @inheritdoc ERC165
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IServiceQualityOracle).interfaceId || super.supportsInterface(interfaceId);
}
}
2 changes: 1 addition & 1 deletion packages/contracts/task/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"rootDir": ".",
"rootDir": "..",
"outDir": "./build",
"declarationDir": "./types"
},
Expand Down
Loading
Loading