Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
538e004
feat: issuance allocation contracts
RembrandtK Oct 8, 2025
01b12fe
fix: resolve coverage build logic error after contract changes
RembrandtK Oct 8, 2025
d811ba5
feat: integrate IssuanceAllocator into RewardsManager
RembrandtK Oct 8, 2025
121337f
feat: add interface ID auto-generation system
RembrandtK Oct 8, 2025
1eef7b1
fix: subgraph-service MockRewardsManager
RembrandtK Oct 8, 2025
afbb544
feat: move interface ID generation to interfaces package
RembrandtK Oct 8, 2025
8ffbbdd
fix: resolve lint issues and centralize interface ID generation
RembrandtK Oct 8, 2025
82ec319
fix: address PR comments and improve import consistency
RembrandtK Oct 8, 2025
65f0947
chore: removing unused code
RembrandtK Oct 9, 2025
25cfb80
docs(issuance): add README and remove LICENSE reference
RembrandtK Oct 9, 2025
080b252
docs(issuance): add contract documentation references to README
RembrandtK Oct 9, 2025
e17797a
feat: not only IA can call beforeIssuanceAllocationChange()
RembrandtK Oct 9, 2025
b589fee
feat: replace Python interface ID generation with Node.js
RembrandtK Oct 13, 2025
384252f
chore: lint fixes
RembrandtK Oct 13, 2025
d90c57d
feat: split IIssuanceAllocator into role-based sub-interfaces
RembrandtK Oct 13, 2025
ebe8f73
feat: split IRewardsEligibilityOracle into role-based sub-interfaces
RembrandtK Oct 13, 2025
be61051
fix: add missing override keywords to interface implementations
RembrandtK Oct 13, 2025
ce07a80
feat: add complete interface coverage for issuance contracts
RembrandtK Oct 13, 2025
dc800f3
feat: pnpm upgrade
RembrandtK Oct 14, 2025
0e9abee
chore: add Codecov config to ignore test and mock files
RembrandtK Oct 14, 2025
f54f5de
feat: enhance solhint-disable verification script with unit tests
RembrandtK Oct 14, 2025
1b32efa
fix: remove unnecessary solhint-disable rule in MockIssuanceAllocator
RembrandtK Oct 14, 2025
cc386dc
refactor: use ERC165Upgradeable pattern in RewardsManager
RembrandtK Oct 14, 2025
894b410
refactor: implement direct IERC165 in RewardsManager
RembrandtK Oct 14, 2025
e28473c
feat: upgrade OpenZeppelin contracts from v3.4.1 to v3.4.2
RembrandtK Oct 7, 2025
e5d4d8f
chore: merge improvements from build-lint-upgrade
RembrandtK Oct 14, 2025
80debce
refactor: convert TODO check from Python to JavaScript
RembrandtK Oct 14, 2025
a2cffec
refactor: convert bytecode comparison script from Python to JavaScript
RembrandtK Oct 14, 2025
f476866
refactor: convert solhint verification scripts from CommonJS to ES mo…
RembrandtK Oct 14, 2025
3056af5
chore: removing unneeded solhint-disables
RembrandtK Oct 14, 2025
01f4768
chore: removing redundant test file
RembrandtK Oct 14, 2025
991f392
chore: comment cleanup
RembrandtK Oct 14, 2025
20f40a5
chore: standardize package.json metadata and structure across packages
RembrandtK Oct 14, 2025
a5dd22a
test: separate issuance-related tests from rewards.test.ts
RembrandtK Oct 14, 2025
201f618
test: combine duplicate issuance allocator test files
RembrandtK Oct 14, 2025
e84c35e
chore: moving IERC165 to start
RembrandtK Oct 14, 2025
855d0f9
chore: removing unneeded comment
RembrandtK Oct 14, 2025
6320402
chore: restoring toolshed author
RembrandtK Oct 14, 2025
7b0634b
fix: test to take test files from config
RembrandtK Oct 14, 2025
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 codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
coverage:
status:
project:
default:
informational: true
patch:
default:
informational: true

ignore:
- '**/tests/**'
- '**/test/**'
- '**/*.test.sol'
- '**/*.t.sol'
- '**/Mock*.sol'
- '**/mocks/**'
- '**/scripts/**'
4 changes: 4 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ const eslintConfig = [
...globals.mocha,
},
},
rules: {
// Allow 'any' types in test files where they're often necessary for testing edge cases
'@typescript-eslint/no-explicit-any': 'off',
},
},

// Add Hardhat globals for hardhat config files
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@
"clean": "pnpm -r run clean",
"clean:all": "pnpm clean && rm -rf node_modules packages/*/node_modules packages/*/*/node_modules",
"build": "pnpm -r run build:self",
"todo": "node scripts/check-todos.mjs",
"lint": "pnpm lint:ts; pnpm lint:sol; pnpm lint:md; pnpm lint:json; pnpm lint:yaml",
"lint:staged": "lint-staged",
"lint:staged": "lint-staged; pnpm todo",
"lint:ts": "eslint --fix --cache '**/*.{js,ts,cjs,mjs,jsx,tsx}'; prettier -w --cache --log-level warn '**/*.{js,ts,cjs,mjs,jsx,tsx}'",
"lint:sol": "scripts/check-todos.sh; pnpm -r run lint:sol; prettier -w --cache --log-level warn '**/*.sol'",
"lint:sol": "pnpm -r run lint:sol; prettier -w --cache --log-level warn '**/*.sol'; pnpm todo",
"lint:md": "markdownlint --fix --ignore-path .gitignore --ignore-path .markdownlintignore '**/*.md'; prettier -w --cache --log-level warn '**/*.md'",
"lint:json": "prettier -w --cache --log-level warn '**/*.json'",
"lint:yaml": "npx yaml-lint .github/**/*.{yml,yaml} packages/contracts/task/config/*.yml; prettier -w --cache --log-level warn '**/*.{yml,yaml}'",
"format": "prettier -w --cache --log-level warn '**/*.{js,ts,cjs,mjs,jsx,tsx,json,md,yaml,yml}'",
"test": "pnpm build && pnpm -r run test:self",
"test:coverage": "pnpm build && pnpm -r run test:coverage:self"
"test:coverage": "pnpm build && pnpm -r run build:self:coverage && pnpm -r run test:coverage:self"
},
"devDependencies": {
"@changesets/cli": "catalog:",
Expand Down Expand Up @@ -60,7 +61,6 @@
"scripts/lint-staged-run.sh 'prettier -w --cache --log-level warn'"
],
"*.sol": [
"scripts/check-todos.sh",
"solhint --fix --noPrompt --noPoster",
"prettier -w --cache --log-level warn"
],
Expand Down
2 changes: 1 addition & 1 deletion packages/address-book/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"access": "public"
},
"description": "Contract addresses for The Graph Protocol",
"author": "The Graph core devs",
"author": "Edge & Node",
"license": "GPL-2.0-or-later",
"exports": {
"./horizon/addresses.json": "./src/horizon/addresses.json",
Expand Down
106 changes: 95 additions & 11 deletions packages/contracts/contracts/rewards/RewardsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
// 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";
Expand All @@ -15,9 +15,11 @@
import { IGraphToken } from "../token/IGraphToken.sol";

import { RewardsManagerV6Storage } from "./RewardsManagerStorage.sol";
import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol";
import { IRewardsIssuer } from "./IRewardsIssuer.sol";
import { IRewardsEligibilityOracle } from "@graphprotocol/interfaces/contracts/issuance/eligibility/IRewardsEligibilityOracle.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 @@ -29,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 @@ -39,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 RewardsManagerV6Storage, 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 Down Expand Up @@ -85,6 +91,13 @@
*/
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
Expand Down Expand Up @@ -113,11 +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 The issuance is defined as a fixed amount of rewards per block in GRT.
* @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.
*
* 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 @@ -171,24 +200,66 @@
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 IRewardsEligibilityOracle interface
// Check that the contract supports the IRewardsEligibility interface
// Allow zero address to disable the oracle
if (newRewardsEligibilityOracle != address(0)) {
require(
IERC165(newRewardsEligibilityOracle).supportsInterface(type(IRewardsEligibilityOracle).interfaceId),
"Contract does not support IRewardsEligibilityOracle interface"
IERC165(newRewardsEligibilityOracle).supportsInterface(type(IRewardsEligibility).interfaceId),
"Contract does not support IRewardsEligibility interface"
);
}

address oldRewardsEligibilityOracle = address(rewardsEligibilityOracle);
rewardsEligibilityOracle = IRewardsEligibilityOracle(newRewardsEligibilityOracle);
rewardsEligibilityOracle = IRewardsEligibility(newRewardsEligibilityOracle);
emit RewardsEligibilityOracleSet(oldRewardsEligibilityOracle, newRewardsEligibilityOracle);
}
}
Expand Down Expand Up @@ -221,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 @@ -238,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 @@ -250,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
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

pragma solidity ^0.7.6 || 0.8.27;

import { IRewardsEligibilityOracle } from "@graphprotocol/interfaces/contracts/issuance/eligibility/IRewardsEligibilityOracle.sol";
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 "./IRewardsIssuer.sol";
import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol";

Check warning on line 13 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/contracts/rewards/IRewardsManager.sol
import { Managed } from "../governance/Managed.sol";

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

Expand All @@ -81,8 +83,11 @@
* @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
IRewardsEligibilityOracle public rewardsEligibilityOracle;
IRewardsEligibility public rewardsEligibilityOracle;
/// @notice Address of the issuance allocator
IIssuanceAllocationDistribution public issuanceAllocator;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

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 IRewardsEligibilityOracle
* @notice A mock contract that supports ERC-165 but not IRewardsEligibility
* @dev Used for testing ERC-165 interface checking in RewardsManager
*/
contract MockERC165OnlyContract is ERC165 {
Expand Down
Loading
Loading