Skip to content

Commit 538e004

Browse files
committed
feat: issuance allocation contracts
1 parent f2dca10 commit 538e004

33 files changed

+7481
-254
lines changed

eslint.config.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,10 @@ const eslintConfig = [
225225
...globals.mocha,
226226
},
227227
},
228+
rules: {
229+
// Allow 'any' types in test files where they're often necessary for testing edge cases
230+
'@typescript-eslint/no-explicit-any': 'off',
231+
},
228232
},
229233

230234
// Add Hardhat globals for hardhat config files

packages/contracts/test/hardhat.config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ const config: HardhatUserConfig = {
6060
// Graph Protocol extensions
6161
graphConfig: path.join(configDir, 'graph.hardhat.yml'),
6262
addressBook: process.env.ADDRESS_BOOK || 'addresses.json',
63-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6463
} as any,
6564
localhost: {
6665
chainId: 1337,
@@ -75,7 +74,6 @@ const config: HardhatUserConfig = {
7574
currency: 'USD',
7675
outputFile: 'reports/gas-report.log',
7776
},
78-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7977
} as any
8078

8179
export default config
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
pragma solidity ^0.7.6 || ^0.8.0;
4+
pragma abicoder v2;
5+
6+
/**
7+
* @notice Target issuance per block information
8+
* @param allocatorIssuancePerBlock Issuance per block for allocator-minting (non-self-minting)
9+
* @param allocatorIssuanceBlockAppliedTo The block up to which allocator issuance has been applied
10+
* @param selfIssuancePerBlock Issuance per block for self-minting
11+
* @param selfIssuanceBlockAppliedTo The block up to which self issuance has been applied
12+
*/
13+
struct TargetIssuancePerBlock {
14+
uint256 allocatorIssuancePerBlock;
15+
uint256 allocatorIssuanceBlockAppliedTo;
16+
uint256 selfIssuancePerBlock;
17+
uint256 selfIssuanceBlockAppliedTo;
18+
}
19+
20+
/**
21+
* @notice Allocation information
22+
* @param totalAllocationPPM Total allocation in PPM (allocatorMintingAllocationPPM + selfMintingAllocationPPM)
23+
* @param allocatorMintingPPM Allocator-minting allocation in PPM (Parts Per Million)
24+
* @param selfMintingPPM Self-minting allocation in PPM (Parts Per Million)
25+
*/
26+
struct Allocation {
27+
uint256 totalAllocationPPM;
28+
uint256 allocatorMintingPPM;
29+
uint256 selfMintingPPM;
30+
}
31+
32+
/**
33+
* @notice Allocation target information
34+
* @param allocatorMintingPPM The allocator-minting allocation amount in PPM (Parts Per Million)
35+
* @param selfMintingPPM The self-minting allocation amount in PPM (Parts Per Million)
36+
* @param lastChangeNotifiedBlock Last block when this target was notified of changes
37+
*/
38+
struct AllocationTarget {
39+
uint256 allocatorMintingPPM;
40+
uint256 selfMintingPPM;
41+
uint256 lastChangeNotifiedBlock;
42+
}
43+
44+
/**
45+
* @title IIssuanceAllocator
46+
* @author Edge & Node
47+
* @notice Interface for the IssuanceAllocator contract, which is responsible for
48+
* allocating token issuance to different components of the protocol.
49+
*
50+
* @dev The allocation model distinguishes between two types of targets:
51+
* 1. Self-minting contracts: These can mint tokens themselves and are supported
52+
* primarily for backwards compatibility with existing contracts.
53+
* 2. Non-self-minting contracts: These cannot mint tokens themselves and rely on
54+
* their issuanceallocator to mint tokens for them.
55+
*/
56+
interface IIssuanceAllocator {
57+
/**
58+
* @notice Distribute issuance to allocated non-self-minting targets.
59+
* @return Block number that issuance has beee distributed to. That will normally be the current block number, unless the contract is paused.
60+
*
61+
* @dev When the contract is paused, no issuance is distributed and lastIssuanceBlock is not updated.
62+
*/
63+
function distributeIssuance() external returns (uint256);
64+
65+
/**
66+
* @notice Set the issuance per block.
67+
* @param newIssuancePerBlock New issuance per block
68+
* @param evenIfDistributionPending If true, set even if there is pending issuance distribution
69+
* @return True if the value is applied (including if already the case), false if not applied due to paused state
70+
*/
71+
function setIssuancePerBlock(uint256 newIssuancePerBlock, bool evenIfDistributionPending) external returns (bool);
72+
73+
/**
74+
* @notice Set the allocation for a target with only allocator minting
75+
* @param target Address of the target to update
76+
* @param allocatorMintingPPM Allocator-minting allocation for the target (in PPM)
77+
* @return True if the value is applied (including if already the case), false if not applied
78+
* @dev This variant sets selfMintingPPM to 0 and evenIfDistributionPending to false
79+
*/
80+
function setTargetAllocation(address target, uint256 allocatorMintingPPM) external returns (bool);
81+
82+
/**
83+
* @notice Set the allocation for a target with both allocator and self minting
84+
* @param target Address of the target to update
85+
* @param allocatorMintingPPM Allocator-minting allocation for the target (in PPM)
86+
* @param selfMintingPPM Self-minting allocation for the target (in PPM)
87+
* @return True if the value is applied (including if already the case), false if not applied
88+
* @dev This variant sets evenIfDistributionPending to false
89+
*/
90+
function setTargetAllocation(
91+
address target,
92+
uint256 allocatorMintingPPM,
93+
uint256 selfMintingPPM
94+
) external returns (bool);
95+
96+
/**
97+
* @notice Set the allocation for a target
98+
* @param target Address of the target to update
99+
* @param allocatorMintingPPM Allocator-minting allocation for the target (in PPM)
100+
* @param selfMintingPPM Self-minting allocation for the target (in PPM)
101+
* @param evenIfDistributionPending Whether to force the allocation change even if issuance has not been distributed up to the current block
102+
* @return True if the value is applied (including if already the case), false if not applied
103+
*/
104+
function setTargetAllocation(
105+
address target,
106+
uint256 allocatorMintingPPM,
107+
uint256 selfMintingPPM,
108+
bool evenIfDistributionPending
109+
) external returns (bool);
110+
111+
/**
112+
* @notice Notify a specific target about an upcoming allocation change
113+
* @param target Address of the target to notify
114+
* @return True if notification was sent or already sent this block, false otherwise
115+
*/
116+
function notifyTarget(address target) external returns (bool);
117+
118+
/**
119+
* @notice Force set the lastChangeNotifiedBlock for a target to a specific block number
120+
* @param target Address of the target to update
121+
* @param blockNumber Block number to set as the lastChangeNotifiedBlock
122+
* @return The block number that was set
123+
* @dev This can be used to enable notification to be sent again (by setting to a past block)
124+
* @dev or to prevent notification until a future block (by setting to current or future block).
125+
*/
126+
function forceTargetNoChangeNotificationBlock(address target, uint256 blockNumber) external returns (uint256);
127+
128+
/**
129+
* @notice Distribute any pending accumulated issuance to allocator-minting targets.
130+
* @return Block number up to which issuance has been distributed
131+
* @dev This function can be called even when the contract is paused.
132+
* @dev If there is no pending issuance, this function is a no-op.
133+
* @dev If allocatorMintingAllowance is 0 (all targets are self-minting), this function is a no-op.
134+
*/
135+
function distributePendingIssuance() external returns (uint256);
136+
137+
/**
138+
* @notice Distribute any pending accumulated issuance to allocator-minting targets, accumulating up to a specific block.
139+
* @param toBlockNumber The block number to accumulate pending issuance up to (must be >= lastIssuanceAccumulationBlock and <= current block)
140+
* @return Block number up to which issuance has been distributed
141+
* @dev This function can be called even when the contract is paused.
142+
* @dev Accumulates pending issuance up to the specified block, then distributes all accumulated issuance.
143+
* @dev If there is no pending issuance after accumulation, this function is a no-op for distribution.
144+
* @dev If allocatorMintingAllowance is 0 (all targets are self-minting), this function is a no-op for distribution.
145+
*/
146+
function distributePendingIssuance(uint256 toBlockNumber) external returns (uint256);
147+
148+
/**
149+
* @notice Get the current allocation for a target
150+
* @param target Address of the target
151+
* @return Allocation struct containing total, allocator-minting, and self-minting allocations
152+
*/
153+
function getTargetAllocation(address target) external view returns (Allocation memory);
154+
155+
/**
156+
* @notice Get the current global allocation totals
157+
* @return Allocation struct containing total, allocator-minting, and self-minting allocations across all targets
158+
*/
159+
function getTotalAllocation() external view returns (Allocation memory);
160+
161+
/**
162+
* @notice Get all allocated target addresses
163+
* @return Array of target addresses
164+
*/
165+
function getTargets() external view returns (address[] memory);
166+
167+
/**
168+
* @notice Get a specific allocated target address by index
169+
* @param index The index of the target address to retrieve
170+
* @return The target address at the specified index
171+
*/
172+
function getTargetAt(uint256 index) external view returns (address);
173+
174+
/**
175+
* @notice Get the number of allocated targets
176+
* @return The total number of allocated targets
177+
*/
178+
function getTargetCount() external view returns (uint256);
179+
180+
/**
181+
* @notice Target issuance per block information
182+
* @param target Address of the target
183+
* @return TargetIssuancePerBlock struct containing allocatorIssuanceBlockAppliedTo, selfIssuanceBlockAppliedTo, allocatorIssuancePerBlock, and selfIssuancePerBlock
184+
* @dev This function does not revert when paused, instead the caller is expected to correctly read and apply the information provided.
185+
* @dev Targets should check allocatorIssuanceBlockAppliedTo and selfIssuanceBlockAppliedTo - if either is not the current block, that type of issuance is paused for that target.
186+
* @dev Targets should not check the allocator's pause state directly, but rely on the blockAppliedTo fields to determine if issuance is paused.
187+
*/
188+
function getTargetIssuancePerBlock(address target) external view returns (TargetIssuancePerBlock memory);
189+
190+
/**
191+
* @notice Get the current issuance per block
192+
* @return The current issuance per block
193+
*/
194+
function issuancePerBlock() external view returns (uint256);
195+
196+
/**
197+
* @notice Get the last block number where issuance was distributed
198+
* @return The last block number where issuance was distributed
199+
*/
200+
function lastIssuanceDistributionBlock() external view returns (uint256);
201+
202+
/**
203+
* @notice Get the last block number where issuance was accumulated during pause
204+
* @return The last block number where issuance was accumulated during pause
205+
*/
206+
function lastIssuanceAccumulationBlock() external view returns (uint256);
207+
208+
/**
209+
* @notice Get the amount of pending accumulated allocator issuance
210+
* @return The amount of pending accumulated allocator issuance
211+
*/
212+
function pendingAccumulatedAllocatorIssuance() external view returns (uint256);
213+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
pragma solidity ^0.7.6 || ^0.8.0;
4+
5+
/**
6+
* @title IIssuanceTarget
7+
* @author Edge & Node
8+
* @notice Interface for contracts that receive issuance from an issuance allocator
9+
*/
10+
interface IIssuanceTarget {
11+
/**
12+
* @notice Called by the issuance allocator before the target's issuance allocation changes
13+
* @dev The target should ensure that all issuance related calculations are up-to-date
14+
* with the current block so that an allocation change can be applied correctly.
15+
* Note that the allocation could change multiple times in the same block after
16+
* this function has been called, only the final allocation is relevant.
17+
*/
18+
function beforeIssuanceAllocationChange() external;
19+
20+
/**
21+
* @notice Sets the issuance allocator for this target
22+
* @dev This function facilitates upgrades by providing a standard way for targets
23+
* to change their allocator. Implementations can define their own access control.
24+
* @param issuanceAllocator Address of the issuance allocator
25+
*/
26+
function setIssuanceAllocator(address issuanceAllocator) external;
27+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
pragma solidity 0.8.27;
4+
5+
import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol";
6+
import { BaseUpgradeable } from "../common/BaseUpgradeable.sol";
7+
8+
// solhint-disable-next-line no-unused-import
9+
import { ERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; // Used by @inheritdoc
10+
11+
/**
12+
* @title DirectAllocation
13+
* @author Edge & Node
14+
* @notice A simple contract that receives tokens from the IssuanceAllocator and allows
15+
* an authorized operator to withdraw them.
16+
*
17+
* @dev This contract is designed to be a non-self-minting target in the IssuanceAllocator.
18+
* The IssuanceAllocator will mint tokens directly to this contract, and the authorized
19+
* operator can send them to individual addresses as needed.
20+
*
21+
* This contract is pausable by the PAUSE_ROLE. When paused, tokens cannot be sent.
22+
* @custom:security-contact Please email [email protected] if you find any bugs. We might have an active bug bounty program.
23+
*/
24+
contract DirectAllocation is BaseUpgradeable, IIssuanceTarget {
25+
// -- Events --
26+
27+
/// @notice Emitted when tokens are sent
28+
/// @param to The address that received the tokens
29+
/// @param amount The amount of tokens sent
30+
event TokensSent(address indexed to, uint256 amount); // solhint-disable-line gas-indexed-events
31+
// Do not need to index amount, ignoring gas-indexed-events warning.
32+
33+
/// @notice Emitted before the issuance allocation changes
34+
event BeforeIssuanceAllocationChange();
35+
36+
// -- Constructor --
37+
38+
/**
39+
* @notice Constructor for the DirectAllocation contract
40+
* @dev This contract is upgradeable, but we use the constructor to pass the Graph Token address
41+
* to the base contract.
42+
* @param graphToken Address of the Graph Token contract
43+
* @custom:oz-upgrades-unsafe-allow constructor
44+
*/
45+
constructor(address graphToken) BaseUpgradeable(graphToken) {}
46+
47+
// -- Initialization --
48+
49+
/**
50+
* @notice Initialize the DirectAllocation contract
51+
* @param governor Address that will have the GOVERNOR_ROLE
52+
*/
53+
function initialize(address governor) external virtual initializer {
54+
__BaseUpgradeable_init(governor);
55+
}
56+
57+
// -- ERC165 --
58+
59+
/**
60+
* @inheritdoc ERC165Upgradeable
61+
*/
62+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
63+
return interfaceId == type(IIssuanceTarget).interfaceId || super.supportsInterface(interfaceId);
64+
}
65+
66+
// -- External Functions --
67+
68+
/**
69+
* @notice Send tokens to a specified address
70+
* @dev This function can only be called by accounts with the OPERATOR_ROLE
71+
* @param to Address to send tokens to
72+
* @param amount Amount of tokens to send
73+
*/
74+
function sendTokens(address to, uint256 amount) external onlyRole(OPERATOR_ROLE) whenNotPaused {
75+
// TODO: missed for audit, should change to custom error in furture
76+
// solhint-disable-next-line gas-custom-errors
77+
require(GRAPH_TOKEN.transfer(to, amount), "!transfer");
78+
emit TokensSent(to, amount);
79+
}
80+
81+
/**
82+
* @dev For DirectAllocation, this is a no-op since we don't need to perform any calculations
83+
* before an allocation change. We simply receive tokens from the IssuanceAllocator.
84+
* @inheritdoc IIssuanceTarget
85+
*/
86+
function beforeIssuanceAllocationChange() external virtual override {
87+
emit BeforeIssuanceAllocationChange();
88+
}
89+
90+
/**
91+
* @inheritdoc IIssuanceTarget
92+
*/
93+
function setIssuanceAllocator(address issuanceAllocator) external virtual override onlyRole(GOVERNOR_ROLE) {
94+
// No-op for DirectAllocation
95+
// This contract doesn't need to store the issuance allocator
96+
}
97+
}

0 commit comments

Comments
 (0)