Skip to content

Conversation

@RembrandtK
Copy link
Contributor

@RembrandtK RembrandtK commented Nov 17, 2025

Rebase of: #1245

Issuance Allocator contracts, see: packages/issuance/contracts/allocate/IssuanceAllocator.md

Cherry-picked from ec0c984 (issuance-baseline-2/3)
Rebased onto main with regenerated lockfile
@socket-security
Copy link

socket-security bot commented Nov 17, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​nomicfoundation/​hardhat-toolbox@​5.0.0981007688100
Added@​openzeppelin/​hardhat-upgrades@​3.9.19910010087100

View full report

@RembrandtK RembrandtK mentioned this pull request Nov 17, 2025
@openzeppelin-code
Copy link

openzeppelin-code bot commented Nov 17, 2025

Issuance Allocator (IA) (rebased)

Generated at commit: 89f1321c421f03c7f91bcbbfc242609b67c3d441

🚨 Report Summary

Severity Level Results
Contracts Critical
High
Medium
Low
Note
Total
3
5
0
14
38
60
Dependencies Critical
High
Medium
Low
Note
Total
0
0
0
0
0
0

For more details view the full report in OpenZeppelin Code Inspector

@codecov
Copy link

codecov bot commented Nov 17, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.41%. Comparing base (380f6ad) to head (89f1321).
⚠️ Report is 21 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1257      +/-   ##
==========================================
+ Coverage   84.05%   86.41%   +2.36%     
==========================================
  Files          42       45       +3     
  Lines        2070     2393     +323     
  Branches      615      714      +99     
==========================================
+ Hits         1740     2068     +328     
+ Misses        330      325       -5     
Flag Coverage Δ
unittests 86.41% <100.00%> (+2.36%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The default allocation receives issuance that is
not explicitly allocated to another target.

- Added setDefaultAllocationAddress() to set.
- getTotalAllocation() excludes default when it is the zero address.
- Tests and documentation updated.
Added two configurable reclaim addresses:

- indexerEligibilityReclaimAddress
- subgraphDeniedReclaimAddress

When rewards are denied and the reclaim address is set (non-zero),
tokens are minted to that address instead of not being minted at all.
Defaults to address(0) (original behavior).

Also updated associated documentation and tests.
- Add clarity to availablePPM calculation explaining it comprises default allocation's allocator-minting PPM, target's allocator-minting PPM, and target's self-minting PPM to maintain 100% allocation invariant
- Refine reentrancy comment to explicitly reference that calculations occur after notifications to prevent reentrancy issues

Addresses PR feedback from code review
…wing (#1268)

Replace vm.assume with bounded inputs to fix "vm.assume rejected too many inputs" error.
The previous implementation used overly restrictive constraints that caused the fuzzer
to reject most random inputs. Now limits amountThawing and amountCollected to half of
MAX_STAKING_TOKENS, guaranteeing valid deposit ranges while maintaining test coverage.
…address

Change setDefaultAllocationAddress to distribute any pending issuance to the
old default address before changing to the new address.

Add evenIfDistributionPending parameter to handle the case when distribution
cannot proceed (e.g., when paused). This allows callers to either:
- Return false when distribution is pending (evenIfDistributionPending=false)
- Force the change anyway (evenIfDistributionPending=true)

Implementation:
- Calls _handleDistributionBeforeAllocation before changing default address
- Distributes pending issuance to old default address up to current block
- Then changes allocation to new default address
- When paused and evenIfDistributionPending=false, returns false instead of changing
- Original setDefaultAllocationAddress(address) signature wraps new implementation with evenIfDistributionPending=false
Refactor _removeTargetFromList to _removeTarget to logically group target
removal operations (array removal and mapping deletion) in a single function.
Loop starts at index 1 to protect default allocation at index 0.

Also fixes MILLION/PPM terminology, adds missing natspec, and adds test for
address(0) default allocation.
When changing default allocation address, allocation data is copied from
oldAddress to newAddress, which was overwriting newAddress's lastChangeNotifiedBlock.

- If newAddress was just notified, the notification record was lost
- If newAddress had a future block set (via forceTargetNoChangeNotificationBlock),
  that restriction was lost
- When changing from address(0) (which is never notified), newAddress's
  lastChangeNotifiedBlock was incorrectly set to 0

Fix: preserve newAddress's lastChangeNotifiedBlock when copying allocation data.

Also clarifies that notification blocks are target-specific, not about the default in general.
IA: Audit fix/response for Issuance Allocator

Incorporated into audit report (Graph_IssuanceAllocator_v01.pdf) and now part of baseline for subsequent.
Response to TRST-L-3 "Inaccurate view functions for the zero-address target"

The current behaviour of getTargetAllocation() and getTargetIssuancePerBlock()
is deliberate and regarded as correct. These functions return assigned
allocations rather than effective minting amounts. This design choice is
supported by the following reasoning:

1. **Semantically correct**: The functions answer "what allocation is assigned
   to this target?" rather than "what does this target receive?".

2. **Consistent interface**: Callers can query the default target allocation
   uniformly, regardless of whether it's address(0) or not.

3. **Accounting utility**: When address(0) is used, the returned values
   represent the unallocated/unminted portion of issuance.

4. **Separation of concerns**: getTotalAllocation() provides the effective
   system-wide allocation that excludes unmintable portions.

The documentation has been enhanced to make this behaviour explicit for the
address(0) edge case, to avoid potential confusion.
Addresses TRST-R-1: Follow naming conventions for non-public functions

Updated two private functions for consistency with the rest of the
codebase:
- notifyAllTargets() → _notifyAllTargets()
- accumulatePendingIssuance() → _accumulatePendingIssuance()

All private and internal functions now consistently use underscore
prefixes, improving code clarity and reducing potential for errors.
…nce for TRST-R-5

Addresses TRST-L-1: "Reward reclaiming efficiency is significantly limited"
Addresses TRST-R-5: "Clarify reclaim precedence"

The TRST-L-1 audit finding identified that reward reclaiming through takeRewards()
is flawed because a rational indexer who would not receive rewards would not
trigger the rewarding logic in the first place. The recommended mitigation is to
add calls from SubgraphService so that any entity can force a reclaim of denied
rewards. (Not adding to StakingExtension as it is deprecated and will soon not
be used.)

The TRST-R-5 finding noted that the precedence when both denial reasons apply
should be documented. Added explicit documentation that subgraph denylist
reclaim is prioritized over indexer eligibility reclaim.

Key changes:

1. Created RewardsReclaim library with canonical reclaim reason constants
   using bytes32 identifiers (like OpenZeppelin roles) for extensibility

2. Migrated RewardsManagerStorage from separate reclaim address storage to
   extensible mapping:
   - Old: indexerEligibilityReclaimAddress, subgraphDeniedReclaimAddress
   - New: mapping(bytes32 => address) reclaimAddresses

3. Replaced type-specific setters with generic setReclaimAddress(bytes32, address)

4. Refactored RewardsManager.takeRewards() with early return for zero rewards
   and extracted helper functions:
   - _calcAllocationRewards(): extract allocation data calculation
   - _reclaimRewards(): common reclaim logic with named return value
   - _deniedRewards(): implements short-circuit pattern with precedence
     (SUBGRAPH_DENIED checked before INDEXER_INELIGIBLE)

5. Added public reclaimRewards() for external contracts to reclaim rewards
   with custom reasons and data

6. Updated AllocationManager._presentPOI() to explicitly call reclaimRewards()
   with specific reasons (STALE_POI, ALTRUISTIC_ALLOCATION, ZERO_POI,
   ALLOCATION_TOO_YOUNG) instead of relying on takeRewards(). This enables
   forced reclaiming of denied rewards, addressing the core TRST-L-1 issue.

7. Updated tests and mocks to use new bytes32-based API with RewardsReclaim
   constants

This allows any entity to force reclaim of denied rewards through SubgraphService,
and enables future extension with new reclaim reasons without contract changes.
…d security improvements

Major refactoring to improve clarity and address audit findings TRST-R-2, TRST-R-3, and TRST-R-4.

Core Changes

PPM to Absolute Rates Migration
- Changed from percentage-based allocations (PPM) to absolute token rates (tokens per block)
- Updated all allocation logic to use rates instead of percentages
- Maintains 100% allocation invariant: totalAllocatorRate + totalSelfMintingRate == issuancePerBlock
- Default target automatically adjusted to maintain invariant

Improved Naming
- Renamed `accumulatedSelfMinting` to `selfMintingOffset` for clarity
- Renamed `setDefaultAllocationAddress` to `setDefaultTarget`
- Event renamed: `DefaultAllocationAddressUpdated` to `DefaultTargetUpdated`
- Field names: `*PPM` to `*Rate` (totalAllocationRate, allocatorMintingRate, selfMintingRate)

API Changes
- Replaced `bool evenIfDistributionPending` parameter with `uint256 minDistributedBlock`
- More flexible control: allows requiring distribution to specific block before config changes
- Translation: `evenIfDistributionPending=true` → `minDistributedBlock=0`

Security Improvements

TRST-R-2: Event Visibility for Self-Minting
- Added `IssuanceSelfMintAllowance` event emitted when self-minting allowances are calculated
- Provides visibility into all minting activity, not just allocator-minting

TRST-R-3: Security Assumption Documentation
- Documented overflow safety assumptions with concrete examples
- Documented initialization block range safety (lastDistributionBlock starts at block.number)
- Added inline comments explaining division-by-zero impossibility
- Clarified pause behavior and block tracking divergence

TRST-R-4: Reentrancy Protection
- Added `ReentrancyGuardTransientUpgradeable` using EIP-1153 transient storage
- Applied to all governance functions that modify configuration
- `distributeIssuance()` intentionally NOT guarded (documented why it's safe)
- Protection against multi-sig signature exploitation vectors

Fix TRST-R-7: Correct pause behavior documentation for setter functions.
Rate changes now correctly documented as applying immediately and being
used retroactively when distribution resumes, rather than applying from
lastDistributionBlock.

Update documentation to reflect migration from PPM-based proportional
allocation to absolute rate-based system (tokens per block):
- Update all storage variable names and descriptions
- Replace PPM terminology with rate-based terminology throughout
- Document new default target mechanism and 100% allocation invariant
- Update function signatures to use minDistributedBlock instead of
  evenIfDistributionPending parameter
- Add documentation for new setDefaultTarget functions
- Update all event signatures with new parameters
- Add new error conditions and update existing ones
- Update view function return types and descriptions
- Clarify that notifications occur even when paused
Addresses TRST-M-3 audit finding by documenting the mathematical
relationship between self-minting and allocator-minting.

Changes:
- Add comprehensive documentation of 5 key invariants in IssuanceAllocator.sol
  proving total issuance never exceeds Σ(issuancePerBlock_b) for any period
- Remove duplicate sections (Events, Error Conditions, Storage) from
  IssuanceAllocator.md that are better documented in NatSpec
- Add WIP Initial Setup deployment sequence to IssuanceAllocator.md
Addresses TRST-M-3 audit finding.

Events for offset adjustments of accumulation and during pending
distribution, emitting whenever offset changes and showing both
before and after states for complete accounting transparency.
Addresses TRST-L-5 by adding configurable event emission to prevent
potential gas limit deadlock in _advanceSelfMintingBlock().

Adds three modes controllable by governance:
- None (0): Skip event emission entirely (lowest gas)
- Aggregate (1): Single aggregated event (medium gas)
- PerTarget (2): Per-target events (highest gas, default)

Defaults to PerTarget to provide event visibility by default.
Governance can switch to None or Aggregate to reduce gas costs
as target count grows.

Implementation:
- Add SelfMintingEventMode enum to IIssuanceAllocatorTypes
- Add selfMintingEventMode field to storage struct
- Add IssuanceSelfMintAllowanceAggregate event
- Add SelfMintingEventModeUpdated event
- Add setSelfMintingEventMode(mode) governance function
- Add getSelfMintingEventMode() view function
- Modify _advanceSelfMintingBlock() to respect emission mode
- Initialize to PerTarget in initialize()
…adeable version

Replace ReentrancyGuardTransientUpgradeable (deprecated in OpenZeppelin 5.5+)
with ReentrancyGuardTransient. The non-upgradeable version is appropriate since
the guard only uses transient storage and has no upgradeable state.

Resolves TRST-R-10
Add comments clarifying that accRewardsPending tracking is incomplete
due to _presentPOI clearing pending even when rewards are not consumed.

Addresses audit finding TRST-L-4 as acknowledged limitation.
Audited with all issues reported fixed in Graph_IssuanceAllocator_v03.
@RembrandtK
Copy link
Contributor Author

RembrandtK commented Dec 31, 2025

Audited with all issues marked fixed or (for one low severity issue) acknowledged in Graph_IssuanceAllocator_v03 for commit hash f283815.

@RembrandtK RembrandtK marked this pull request as ready for review December 31, 2025 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants