Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8c08f3a
feat: baseline issuance package
RembrandtK Nov 16, 2025
dbf5d2b
feat: issuance allocator
RembrandtK Oct 22, 2025
9f6c704
feat: default issuance allocation for Issuance Allocator
RembrandtK Dec 12, 2025
13a77e4
feat: optional issunace reclaim addresses for Rewards Manager
RembrandtK Dec 12, 2025
70c00e4
docs: improve code comments in IssuanceAllocator
RembrandtK Dec 12, 2025
cbe2bd1
fix: resolve fuzz test failure in testGetBalance_WhenCollectedOverTha…
RembrandtK Dec 12, 2025
ba9c466
feat: distribute pending issuance before changing default allocation …
RembrandtK Dec 15, 2025
cc652ae
refactor: improve IssuanceAllocator target removal
RembrandtK Dec 15, 2025
88ce412
fix: preserve notification state when changing default allocation
RembrandtK Dec 15, 2025
48be37a
Merge pull request #1267 from graphprotocol/issuance-allocator-6
RembrandtK Dec 16, 2025
55c2537
docs: clarify view function behaviour for address(0) default allocation
RembrandtK Dec 16, 2025
8edca9a
refactor: add underscore prefix to private functions
RembrandtK Dec 16, 2025
b8e8159
fix: enable forced reward reclaiming for TRST-L-1 and clarify precede…
RembrandtK Dec 19, 2025
d2e83ed
refactor: migrate IssuanceAllocator from PPM to absolute rates and ad…
RembrandtK Dec 19, 2025
0ccf381
fixup! refactor: migrate IssuanceAllocator from PPM to absolute rates…
RembrandtK Dec 20, 2025
9488b9d
fixup! refactor: migrate IssuanceAllocator from PPM to absolute rates…
RembrandtK Dec 20, 2025
8f5c074
docs: add issuance accounting invariants and refactor documentation
RembrandtK Dec 30, 2025
f1337d9
feat: add events for self-minting offset visibility
RembrandtK Dec 30, 2025
aedce7c
feat: add governance-controlled self-minting event emission modes
RembrandtK Dec 30, 2025
32c6809
fix: replace legacy ReentrancyGuardTransientUpgradeable with non-upgr…
RembrandtK Dec 30, 2025
468f176
docs: note incomplete tracking of unconsumed rewards
RembrandtK Dec 30, 2025
f283815
fixup! feat: add governance-controlled self-minting event emission modes
RembrandtK Dec 31, 2025
89f1321
Merge pull request #1272 from graphprotocol/issuance-allocator-3-fix-2
RembrandtK Dec 31, 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
360 changes: 325 additions & 35 deletions packages/contracts/contracts/rewards/RewardsManager.sol

Large diffs are not rendered by default.

18 changes: 18 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";
import { IRewardsEligibility } from "@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,19 @@ contract RewardsManagerV5Storage is RewardsManagerV4Storage {
/// @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, Issuance Allocator, and reclaim addresses.
*/
contract RewardsManagerV6Storage is RewardsManagerV5Storage {
/// @notice Address of the rewards eligibility oracle contract
IRewardsEligibility public rewardsEligibilityOracle;
/// @notice Address of the issuance allocator
IIssuanceAllocationDistribution public issuanceAllocator;
/// @notice Mapping of reclaim reason identifiers to reclaim addresses
/// @dev Uses bytes32 for extensibility. See RewardsReclaim library for canonical reasons.
mapping(bytes32 => address) public reclaimAddresses;
}
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({
allocatorIssuanceRate: allocatorIssuance,
allocatorIssuanceBlockAppliedTo: block.number,
selfIssuanceRate: selfIssuance,
selfIssuanceBlockAppliedTo: block.number
});
}

/**
* @inheritdoc IERC165
*/
function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
return
interfaceId == type(IIssuanceAllocationDistribution).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: GPL-2.0-or-later

// solhint-disable named-parameters-mapping

pragma solidity 0.7.6;

import { IRewardsEligibility } from "@graphprotocol/interfaces/contracts/issuance/eligibility/IRewardsEligibility.sol";
import { IERC165 } from "@openzeppelin/contracts/introspection/IERC165.sol";

/**
* @title MockRewardsEligibilityOracle
* @author Edge & Node
* @notice A simple mock contract for the RewardsEligibilityOracle interface
* @dev A simple mock contract for the RewardsEligibilityOracle interface
*/
contract MockRewardsEligibilityOracle is IRewardsEligibility, IERC165 {
/// @dev Mapping to store eligibility status for each indexer
mapping(address => bool) private eligible;

/// @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 newDefaultResponse Default response for isEligible
*/
constructor(bool newDefaultResponse) {
defaultResponse = newDefaultResponse;
}

/**
* @notice Set whether a specific indexer is eligible
* @param indexer The indexer address
* @param eligibility Whether the indexer is eligible
*/
function setIndexerEligible(address indexer, bool eligibility) external {
eligible[indexer] = eligibility;
isSet[indexer] = true;
}

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

/**
* @inheritdoc IRewardsEligibility
*/
function isEligible(address indexer) external view override returns (bool) {
// If the indexer has been explicitly set, return that value
if (isSet[indexer]) {
return eligible[indexer];
}

// Otherwise return the default response
return defaultResponse;
}

/**
* @inheritdoc IERC165
*/
function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
return interfaceId == type(IRewardsEligibility).interfaceId || interfaceId == type(IERC165).interfaceId;
}
}
129 changes: 129 additions & 0 deletions packages/contracts/contracts/tests/MockSubgraphService.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// SPDX-License-Identifier: GPL-2.0-or-later

// solhint-disable named-parameters-mapping

pragma solidity 0.7.6;

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

/**
* @title MockSubgraphService
* @author Edge & Node
* @notice A mock contract for testing SubgraphService as a rewards issuer
* @dev Implements IRewardsIssuer interface to simulate SubgraphService behavior in tests
*/
contract MockSubgraphService is IRewardsIssuer {
/// @dev Struct to store allocation data
struct Allocation {
bool isActive;
address indexer;
bytes32 subgraphDeploymentId;
uint256 tokens;
uint256 accRewardsPerAllocatedToken;
uint256 accRewardsPending;
}

/// @dev Mapping of allocation ID to allocation data
mapping(address => Allocation) private allocations;

/// @dev Mapping of subgraph deployment ID to total allocated tokens
mapping(bytes32 => uint256) private subgraphAllocatedTokens;

/**
* @notice Set allocation data for testing
* @param allocationId The allocation ID
* @param isActive Whether the allocation is active
* @param indexer The indexer address
* @param subgraphDeploymentId The subgraph deployment ID
* @param tokens Amount of allocated tokens
* @param accRewardsPerAllocatedToken Rewards snapshot
* @param accRewardsPending Accumulated rewards pending
*/
function setAllocation(
address allocationId,
bool isActive,
address indexer,
bytes32 subgraphDeploymentId,
uint256 tokens,
uint256 accRewardsPerAllocatedToken,
uint256 accRewardsPending
) external {
allocations[allocationId] = Allocation({
isActive: isActive,
indexer: indexer,
subgraphDeploymentId: subgraphDeploymentId,
tokens: tokens,
accRewardsPerAllocatedToken: accRewardsPerAllocatedToken,
accRewardsPending: accRewardsPending
});
}

/**
* @notice Set total allocated tokens for a subgraph
* @param subgraphDeploymentId The subgraph deployment ID
* @param tokens Total tokens allocated
*/
function setSubgraphAllocatedTokens(bytes32 subgraphDeploymentId, uint256 tokens) external {
subgraphAllocatedTokens[subgraphDeploymentId] = tokens;
}

/**
* @inheritdoc IRewardsIssuer
*/
function getAllocationData(
address allocationId
)
external
view
override
returns (
bool isActive,
address indexer,
bytes32 subgraphDeploymentId,
uint256 tokens,
uint256 accRewardsPerAllocatedToken,
uint256 accRewardsPending
)
{
Allocation memory allocation = allocations[allocationId];
return (
allocation.isActive,
allocation.indexer,
allocation.subgraphDeploymentId,
allocation.tokens,
allocation.accRewardsPerAllocatedToken,
allocation.accRewardsPending
);
}

/**
* @inheritdoc IRewardsIssuer
*/
function getSubgraphAllocatedTokens(bytes32 subgraphDeploymentId) external view override returns (uint256) {
return subgraphAllocatedTokens[subgraphDeploymentId];
}

/**
* @notice Helper function to call reclaimRewards on RewardsManager for testing
* @param rewardsManager Address of the RewardsManager contract
* @param reason Reason identifier for reclaiming rewards
* @param allocationId The allocation ID
* @param contextData Additional context data for the reclaim
* @return Amount of rewards reclaimed
*/
function callReclaimRewards(
address rewardsManager,
bytes32 reason,
address allocationId,
bytes calldata contextData
) external returns (uint256) {
// Call reclaimRewards on the RewardsManager
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory data) = rewardsManager.call(
// solhint-disable-next-line gas-small-strings
abi.encodeWithSignature("reclaimRewards(bytes32,address,bytes)", reason, allocationId, contextData)
);
require(success, "reclaimRewards call failed");
return abi.decode(data, (uint256));
}
}
2 changes: 1 addition & 1 deletion packages/contracts/test/.solcover.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const skipFiles = ['bancor', 'ens', 'erc1056', 'arbitrum', 'tests/arbitrum']
const skipFiles = ['bancor', 'ens', 'erc1056', 'arbitrum', 'tests', '*Mock.sol']

module.exports = {
providerOptions: {
Expand Down
Loading