Skip to content

Commit 89f1321

Browse files
authored
Merge pull request #1272 from graphprotocol/issuance-allocator-3-fix-2
Audited with all issues reported fixed in Graph_IssuanceAllocator_v03.
2 parents 9488b9d + f283815 commit 89f1321

File tree

11 files changed

+753
-369
lines changed

11 files changed

+753
-369
lines changed

packages/contracts/contracts/rewards/RewardsManager.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ contract RewardsManager is RewardsManagerV6Storage, GraphUpgradeable, IERC165, I
295295
* @inheritdoc IRewardsManager
296296
* @dev bytes32(0) is reserved as an invalid reason to prevent accidental misconfiguration
297297
* and catch uninitialized reason identifiers.
298+
*
299+
* IMPORTANT: Changes take effect immediately and retroactively. All unclaimed rewards from
300+
* previous periods will be sent to the new reclaim address when they are eventually reclaimed,
301+
* regardless of which address was configured when the rewards were originally accrued.
298302
*/
299303
function setReclaimAddress(bytes32 reason, address newAddress) external override onlyGovernor {
300304
require(reason != bytes32(0), "Cannot set reclaim address for (bytes32(0))");

packages/interfaces/contracts/contracts/rewards/IRewardsManager.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ interface IRewardsManager {
5252
/**
5353
* @notice Set the reclaim address for a specific reason
5454
* @dev Address to mint tokens for denied/reclaimed rewards. Set to zero to disable.
55+
*
56+
* IMPORTANT: Changes take effect immediately and retroactively. All unclaimed rewards from
57+
* previous periods will be sent to the new reclaim address when they are eventually reclaimed,
58+
* regardless of which address was configured when the rewards were originally accrued.
59+
*
5560
* @param reason The reclaim reason identifier (see RewardsReclaim library for canonical reasons)
5661
* @param newReclaimAddress The address to receive tokens
5762
*/

packages/interfaces/contracts/issuance/allocate/IIssuanceAllocationAdministration.sol

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
pragma solidity ^0.7.6 || ^0.8.0;
44

55
import { IIssuanceTarget } from "./IIssuanceTarget.sol";
6+
import { SelfMintingEventMode } from "./IIssuanceAllocatorTypes.sol";
67

78
/**
89
* @title IIssuanceAllocationAdministration
@@ -134,4 +135,21 @@ interface IIssuanceAllocationAdministration {
134135
* @return distributedBlock Block number that issuance was distributed up to
135136
*/
136137
function distributePendingIssuance(uint256 toBlockNumber) external returns (uint256 distributedBlock);
138+
139+
/**
140+
* @notice Set the self-minting event emission mode
141+
* @param newMode The new emission mode (None, Aggregate, or PerTarget)
142+
* @return applied True if the mode was set (including if already set to that mode)
143+
* @dev None: Skip event emission entirely (lowest gas)
144+
* @dev Aggregate: Emit single aggregated event for all self-minting (medium gas)
145+
* @dev PerTarget: Emit events for each target with self-minting (highest gas)
146+
* @dev Self-minting targets should call getTargetIssuancePerBlock() rather than relying on events
147+
*/
148+
function setSelfMintingEventMode(SelfMintingEventMode newMode) external returns (bool applied);
149+
150+
/**
151+
* @notice Get the current self-minting event emission mode
152+
* @return mode The current emission mode
153+
*/
154+
function getSelfMintingEventMode() external view returns (SelfMintingEventMode mode);
137155
}

packages/interfaces/contracts/issuance/allocate/IIssuanceAllocatorTypes.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
pragma solidity ^0.7.6 || ^0.8.0;
44
pragma abicoder v2;
55

6+
/**
7+
* @notice Controls self-minting event emission behavior to manage gas costs
8+
* @dev None skips event emission entirely (lowest gas)
9+
* @dev Aggregate emits a single aggregated event for all self-minting
10+
* @dev PerTarget emits events for each target with self-minting (highest gas)
11+
*/
12+
enum SelfMintingEventMode {
13+
None,
14+
Aggregate,
15+
PerTarget
16+
}
17+
618
/**
719
* @notice Target issuance per block information
820
* @param allocatorIssuanceRate Issuance rate for allocator-minting (tokens per block)

packages/issuance/contracts/allocate/IssuanceAllocator.md

Lines changed: 81 additions & 306 deletions
Large diffs are not rendered by default.

packages/issuance/contracts/allocate/IssuanceAllocator.sol

Lines changed: 211 additions & 62 deletions
Large diffs are not rendered by default.

packages/issuance/contracts/test/allocate/IssuanceAllocatorTestHarness.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ contract IssuanceAllocatorTestHarness is IssuanceAllocator {
1414
/**
1515
* @notice Constructor for the test harness
1616
* @param _graphToken Address of the Graph Token contract
17+
* @custom:oz-upgrades-unsafe-allow constructor
1718
*/
1819
constructor(address _graphToken) IssuanceAllocator(_graphToken) {}
1920

packages/issuance/test/tests/allocate/InterfaceIdStability.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('Allocate Interface ID Stability', () => {
2626
})
2727

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

3232
it('IIssuanceAllocationStatus should have stable interface ID', () => {

packages/issuance/test/tests/allocate/IssuanceAllocator.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1866,6 +1866,37 @@ describe('IssuanceAllocator', () => {
18661866
expect(result).to.equal(currentBlock)
18671867
})
18681868

1869+
it('should not emit SelfMintingOffsetReconciled when offset unchanged', async () => {
1870+
const { issuanceAllocator, graphToken, target1 } = await setupIssuanceAllocator()
1871+
1872+
// Setup with only allocator-minting (no self-minting)
1873+
await (graphToken as any).addMinter(await issuanceAllocator.getAddress())
1874+
await issuanceAllocator.connect(accounts.governor).setIssuancePerBlock(ethers.parseEther('100'))
1875+
await issuanceAllocator
1876+
.connect(accounts.governor)
1877+
['setTargetAllocation(address,uint256,uint256)'](await target1.getAddress(), ethers.parseEther('50'), 0)
1878+
1879+
// Distribute to current block (no accumulated offset)
1880+
await issuanceAllocator.connect(accounts.governor).distributeIssuance()
1881+
1882+
// Verify no offset accumulated
1883+
const stateBefore = await issuanceAllocator.getDistributionState()
1884+
expect(stateBefore.selfMintingOffset).to.equal(0)
1885+
1886+
// Mine blocks and distribute again
1887+
await ethers.provider.send('evm_mine', [])
1888+
await ethers.provider.send('evm_mine', [])
1889+
1890+
// distributePendingIssuance with no accumulated offset should not emit reconciliation event
1891+
const tx = await issuanceAllocator.connect(accounts.governor)['distributePendingIssuance()']()
1892+
1893+
await expect(tx).to.not.emit(issuanceAllocator, 'SelfMintingOffsetReconciled')
1894+
1895+
// Verify offset is still 0
1896+
const stateAfter = await issuanceAllocator.getDistributionState()
1897+
expect(stateAfter.selfMintingOffset).to.equal(0)
1898+
})
1899+
18691900
it('should handle proportional distribution when available < allocatedTotal', async () => {
18701901
const { issuanceAllocator, graphToken, target1, target2 } = await setupIssuanceAllocator()
18711902

0 commit comments

Comments
 (0)