Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 4 additions & 0 deletions packages/contracts/contracts/rewards/RewardsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@
// 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 { 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
import { RewardsReclaim } from "@graphprotocol/interfaces/contracts/contracts/rewards/RewardsReclaim.sol";

Check warning on line 23 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/RewardsReclaim.sol

/**
* @title Rewards Manager Contract
Expand Down Expand Up @@ -295,6 +295,10 @@
* @inheritdoc IRewardsManager
* @dev bytes32(0) is reserved as an invalid reason to prevent accidental misconfiguration
* and catch uninitialized reason identifiers.
*
* IMPORTANT: Changes take effect immediately and retroactively. All unclaimed rewards from
* previous periods will be sent to the new reclaim address when they are eventually reclaimed,
* regardless of which address was configured when the rewards were originally accrued.
*/
function setReclaimAddress(bytes32 reason, address newAddress) external override onlyGovernor {
require(reason != bytes32(0), "Cannot set reclaim address for (bytes32(0))");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ interface IRewardsManager {
/**
* @notice Set the reclaim address for a specific reason
* @dev Address to mint tokens for denied/reclaimed rewards. Set to zero to disable.
*
* IMPORTANT: Changes take effect immediately and retroactively. All unclaimed rewards from
* previous periods will be sent to the new reclaim address when they are eventually reclaimed,
* regardless of which address was configured when the rewards were originally accrued.
*
* @param reason The reclaim reason identifier (see RewardsReclaim library for canonical reasons)
* @param newReclaimAddress The address to receive tokens
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity ^0.7.6 || ^0.8.0;

import { IIssuanceTarget } from "./IIssuanceTarget.sol";
import { SelfMintingEventMode } from "./IIssuanceAllocatorTypes.sol";

/**
* @title IIssuanceAllocationAdministration
Expand Down Expand Up @@ -134,4 +135,21 @@ interface IIssuanceAllocationAdministration {
* @return distributedBlock Block number that issuance was distributed up to
*/
function distributePendingIssuance(uint256 toBlockNumber) external returns (uint256 distributedBlock);

/**
* @notice Set the self-minting event emission mode
* @param newMode The new emission mode (None, Aggregate, or PerTarget)
* @return applied True if the mode was set (including if already set to that mode)
* @dev None: Skip event emission entirely (lowest gas)
* @dev Aggregate: Emit single aggregated event for all self-minting (medium gas)
* @dev PerTarget: Emit events for each target with self-minting (highest gas)
* @dev Self-minting targets should call getTargetIssuancePerBlock() rather than relying on events
*/
function setSelfMintingEventMode(SelfMintingEventMode newMode) external returns (bool applied);

/**
* @notice Get the current self-minting event emission mode
* @return mode The current emission mode
*/
function getSelfMintingEventMode() external view returns (SelfMintingEventMode mode);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
pragma solidity ^0.7.6 || ^0.8.0;
pragma abicoder v2;

/**
* @notice Controls self-minting event emission behavior to manage gas costs
* @dev None skips event emission entirely (lowest gas, default)
* @dev Aggregate emits a single aggregated event for all self-minting
* @dev PerTarget emits events for each target with self-minting (highest gas)
*/
enum SelfMintingEventMode {
None,
Aggregate,
PerTarget
}

/**
* @notice Target issuance per block information
* @param allocatorIssuanceRate Issuance rate for allocator-minting (tokens per block)
Expand Down
387 changes: 81 additions & 306 deletions packages/issuance/contracts/allocate/IssuanceAllocator.md

Large diffs are not rendered by default.

273 changes: 211 additions & 62 deletions packages/issuance/contracts/allocate/IssuanceAllocator.sol

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ contract IssuanceAllocatorTestHarness is IssuanceAllocator {
/**
* @notice Constructor for the test harness
* @param _graphToken Address of the Graph Token contract
* @custom:oz-upgrades-unsafe-allow constructor
*/
constructor(address _graphToken) IssuanceAllocator(_graphToken) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('Allocate Interface ID Stability', () => {
})

it('IIssuanceAllocationAdministration should have stable interface ID', () => {
expect(IIssuanceAllocationAdministration__factory.interfaceId).to.equal('0xd0b6c0e8')
expect(IIssuanceAllocationAdministration__factory.interfaceId).to.equal('0x50d8541d')
})

it('IIssuanceAllocationStatus should have stable interface ID', () => {
Expand Down
31 changes: 31 additions & 0 deletions packages/issuance/test/tests/allocate/IssuanceAllocator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1866,6 +1866,37 @@ describe('IssuanceAllocator', () => {
expect(result).to.equal(currentBlock)
})

it('should not emit SelfMintingOffsetReconciled when offset unchanged', async () => {
const { issuanceAllocator, graphToken, target1 } = await setupIssuanceAllocator()

// Setup with only allocator-minting (no self-minting)
await (graphToken as any).addMinter(await issuanceAllocator.getAddress())
await issuanceAllocator.connect(accounts.governor).setIssuancePerBlock(ethers.parseEther('100'))
await issuanceAllocator
.connect(accounts.governor)
['setTargetAllocation(address,uint256,uint256)'](await target1.getAddress(), ethers.parseEther('50'), 0)

// Distribute to current block (no accumulated offset)
await issuanceAllocator.connect(accounts.governor).distributeIssuance()

// Verify no offset accumulated
const stateBefore = await issuanceAllocator.getDistributionState()
expect(stateBefore.selfMintingOffset).to.equal(0)

// Mine blocks and distribute again
await ethers.provider.send('evm_mine', [])
await ethers.provider.send('evm_mine', [])

// distributePendingIssuance with no accumulated offset should not emit reconciliation event
const tx = await issuanceAllocator.connect(accounts.governor)['distributePendingIssuance()']()

await expect(tx).to.not.emit(issuanceAllocator, 'SelfMintingOffsetReconciled')

// Verify offset is still 0
const stateAfter = await issuanceAllocator.getDistributionState()
expect(stateAfter.selfMintingOffset).to.equal(0)
})

it('should handle proportional distribution when available < allocatedTotal', async () => {
const { issuanceAllocator, graphToken, target1, target2 } = await setupIssuanceAllocator()

Expand Down
Loading
Loading