Skip to content
Merged
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
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)
* @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