@@ -82,7 +82,7 @@ import { ERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/int
8282 * This provides protection against potential issues if the multi-sig governor role were to have known
8383 * signatures that could be exploited by malicious actors to trigger reentrant calls.
8484 *
85- * The `distributeIssuance()` function intentionally does NOT have reentrancy protection to allow
85+ * The `distributeIssuance()` function intentionally does NOT have a reentrancy guard to allow
8686 * legitimate use cases where targets call it during notifications (e.g., to claim pending issuance
8787 * before allocation changes). This is safe because distributeIssuance() has built-in block-tracking
8888 * protection (preventing double-distribution in the same block), makes no external calls that could
@@ -109,7 +109,7 @@ contract IssuanceAllocator is
109109 /// @param issuancePerBlock Total issuance per block across all targets
110110 /// @param lastDistributionBlock Last block when allocator-minting issuance was distributed
111111 /// @param lastSelfMintingBlock Last block when self-minting was advanced
112- /// @param accumulatedSelfMinting Accumulated self -minting during undistributed periods
112+ /// @param selfMintingOffset Self-minting that offsets allocator -minting budget (accumulates during pause, clears on distribution)
113113 /// @param allocationTargets Mapping of target addresses to their allocation data
114114 /// @param targetAddresses Array of all target addresses (including default target at index 0)
115115 /// @param totalSelfMintingRate Total self-minting rate (tokens per block) across all targets
@@ -122,7 +122,7 @@ contract IssuanceAllocator is
122122 uint256 issuancePerBlock;
123123 uint256 lastDistributionBlock;
124124 uint256 lastSelfMintingBlock;
125- uint256 accumulatedSelfMinting ;
125+ uint256 selfMintingOffset ;
126126 mapping (address => AllocationTarget) allocationTargets;
127127 address [] targetAddresses;
128128 uint256 totalSelfMintingRate;
@@ -239,10 +239,11 @@ contract IssuanceAllocator is
239239 * @param _governor Address that will have the GOVERNOR_ROLE
240240 * @dev Initializes with a default target at index 0 set to address(0)
241241 * @dev default target will receive all unallocated issuance (initially 0 until rate is set)
242- * @dev Security : lastDistributionBlock and lastSelfMintingBlock default to 0. First distribution
242+ * @dev Accounting : lastDistributionBlock and lastSelfMintingBlock default to 0. First distribution
243243 * call will calculate for all blocks from 0 to block.number, which is safe because issuancePerBlock
244244 * is 0 at initialization. Once setIssuancePerBlock() is called, it triggers _distributeIssuance()
245245 * which updates lastDistributionBlock to current block, preventing large block ranges in future calls.
246+ * Governance must exercise caution when changing issuancePerBlock while paused to ensure applied to correct block range, see setIssuancePerBlock() documentation.
246247 */
247248 function initialize (address _governor ) external virtual initializer {
248249 __BaseUpgradeable_init (_governor);
@@ -317,7 +318,7 @@ contract IssuanceAllocator is
317318 // Once accumulation starts (during pause), continue through any unpaused periods
318319 // until distribution clears the accumulation. This is conservative and allows
319320 // better recovery when distribution is delayed through pause/unpause cycles.
320- if (paused () || 0 < $.accumulatedSelfMinting ) $.accumulatedSelfMinting += $.totalSelfMintingRate * blocks;
321+ if (paused () || 0 < $.selfMintingOffset ) $.selfMintingOffset += $.totalSelfMintingRate * blocks;
321322 $.lastSelfMintingBlock = block .number ;
322323
323324 // Emit self-minting allowance events
@@ -350,7 +351,7 @@ contract IssuanceAllocator is
350351
351352 if (paused ()) return $.lastDistributionBlock;
352353
353- if (0 < $.accumulatedSelfMinting ) return _distributePendingIssuance (block .number );
354+ if (0 < $.selfMintingOffset ) return _distributePendingIssuance (block .number );
354355 else return _performNormalDistribution ();
355356 }
356357
@@ -439,9 +440,9 @@ contract IssuanceAllocator is
439440 // ~1e33, well below uint256 max (~1e77). Similar multiplications throughout this contract operate
440441 // under the same range assumptions.
441442 uint256 totalForPeriod = $.issuancePerBlock * blocks;
442- uint256 accumulatedSelfMinting = $.accumulatedSelfMinting ;
443+ uint256 selfMintingOffset = $.selfMintingOffset ;
443444
444- uint256 available = accumulatedSelfMinting < totalForPeriod ? totalForPeriod - accumulatedSelfMinting : 0 ;
445+ uint256 available = selfMintingOffset < totalForPeriod ? totalForPeriod - selfMintingOffset : 0 ;
445446
446447 if (0 < available) {
447448 // Calculate non-default allocated rate using the allocation invariant.
@@ -463,11 +464,8 @@ contract IssuanceAllocator is
463464 // Update accumulated self-minting after distribution.
464465 // Subtract the period budget used (min of accumulated and totalForPeriod).
465466 // When caught up to current block, clear all since nothing remains to distribute.
466- if (toBlockNumber == block .number ) $.accumulatedSelfMinting = 0 ;
467- else
468- $.accumulatedSelfMinting = totalForPeriod < accumulatedSelfMinting
469- ? accumulatedSelfMinting - totalForPeriod
470- : 0 ;
467+ if (toBlockNumber == block .number ) $.selfMintingOffset = 0 ;
468+ else $.selfMintingOffset = totalForPeriod < selfMintingOffset ? selfMintingOffset - totalForPeriod : 0 ;
471469
472470 return toBlockNumber;
473471 }
@@ -725,36 +723,8 @@ contract IssuanceAllocator is
725723 return _setTargetAllocation (address (target), allocatorMintingRate, selfMintingRate, minDistributedBlock);
726724 }
727725
728- /**
729- * @notice Internal implementation for setting target allocation
730- * @param target Address of the target to update
731- * @param allocatorMintingRate Allocator-minting rate for the target (tokens per block)
732- * @param selfMintingRate Self-minting rate for the target (tokens per block)
733- * @param minDistributedBlock Minimum block number that distribution must have reached
734- * @return True if the value is applied (including if already the case), false if not applied due to paused state
735- */
736- function _setTargetAllocation (
737- address target ,
738- uint256 allocatorMintingRate ,
739- uint256 selfMintingRate ,
740- uint256 minDistributedBlock
741- ) internal returns (bool ) {
742- if (! _validateTargetAllocation (target, allocatorMintingRate, selfMintingRate)) return true ;
743-
744- if (_distributeIssuance () < minDistributedBlock) return false ;
745-
746- _notifyTarget (target);
747- _validateAndUpdateTotalAllocations (target, allocatorMintingRate, selfMintingRate);
748- _updateTargetAllocationData (target, allocatorMintingRate, selfMintingRate);
749-
750- emit TargetAllocationUpdated (target, allocatorMintingRate, selfMintingRate);
751- return true ;
752- }
753-
754726 /**
755727 * @inheritdoc IIssuanceAllocationAdministration
756- * @dev Delegates to internal implementation with block.number
757- * TODO: Intead of talking about implementation, talk about behavior that requires distribution to current block
758728 */
759729 function setDefaultTarget (
760730 address newAddress
@@ -820,6 +790,42 @@ contract IssuanceAllocator is
820790 return true ;
821791 }
822792
793+ /**
794+ * @notice Internal implementation for setting target allocation
795+ * @param target Address of the target to update
796+ * @param allocatorMintingRate Allocator-minting rate for the target (tokens per block)
797+ * @param selfMintingRate Self-minting rate for the target (tokens per block)
798+ * @param minDistributedBlock Minimum block number that distribution must have reached
799+ * @return True if the value is applied (including if already the case), false if not applied due to paused state
800+ */
801+ function _setTargetAllocation (
802+ address target ,
803+ uint256 allocatorMintingRate ,
804+ uint256 selfMintingRate ,
805+ uint256 minDistributedBlock
806+ ) internal returns (bool ) {
807+ if (! _validateTargetAllocation (target, allocatorMintingRate, selfMintingRate)) return true ;
808+
809+ if (_distributeIssuance () < minDistributedBlock) return false ;
810+
811+ _notifyTarget (target);
812+
813+ // Total allocation calculation and check is delayed until after notifications.
814+ // Distributing and notifying unnecessarily is harmless, but we need to prevent
815+ // reentrancy from looping and changing allocations mid-calculation.
816+ // (Would not be likely to be exploitable due to only governor being able to
817+ // make a call to set target allocation, but better to be paranoid.)
818+ // Validate totals and auto-adjust default allocation BEFORE updating target data
819+ // so we can read the old allocation values
820+ _validateAndUpdateTotalAllocations (target, allocatorMintingRate, selfMintingRate);
821+
822+ // Then update the target's allocation data
823+ _updateTargetAllocationData (target, allocatorMintingRate, selfMintingRate);
824+
825+ emit TargetAllocationUpdated (target, allocatorMintingRate, selfMintingRate);
826+ return true ;
827+ }
828+
823829 /**
824830 * @notice Validates target address and interface support, returns false if allocation is unchanged
825831 * @param target Address of the target to validate
@@ -963,7 +969,7 @@ contract IssuanceAllocator is
963969 DistributionState ({
964970 lastDistributionBlock: $.lastDistributionBlock,
965971 lastSelfMintingBlock: $.lastSelfMintingBlock,
966- accumulatedSelfMinting : $.accumulatedSelfMinting
972+ selfMintingOffset : $.selfMintingOffset
967973 });
968974 }
969975
@@ -1039,27 +1045,15 @@ contract IssuanceAllocator is
10391045 * @dev When default is a real address: returns issuancePerBlock
10401046 * @dev Note: Internally, the contract always maintains 100% allocation invariant, even when default is address(0)
10411047 */
1042- function getTotalAllocation () external view override returns (Allocation memory ) {
1048+ function getTotalAllocation () external view override returns (Allocation memory allocation ) {
10431049 IssuanceAllocatorData storage $ = _getIssuanceAllocatorStorage ();
10441050
1045- uint256 totalAllocatorMinting = $.issuancePerBlock - $.totalSelfMintingRate;
1046- uint256 totalAllocation = $.issuancePerBlock;
1047-
10481051 // If default is address(0), exclude its allocation from reported totals
1049- // since it doesn't actually receive minting (effectively unallocated)
1050- address defaultAddress = $.targetAddresses[0 ];
1051- if (defaultAddress == address (0 )) {
1052- AllocationTarget storage defaultTarget = $.allocationTargets[defaultAddress];
1053- uint256 defaultAllocation = defaultTarget.allocatorMintingRate;
1054- totalAllocatorMinting -= defaultAllocation;
1055- totalAllocation -= defaultAllocation;
1056- }
1057-
1058- return
1059- Allocation ({
1060- totalAllocationRate: totalAllocation,
1061- allocatorMintingRate: totalAllocatorMinting,
1062- selfMintingRate: $.totalSelfMintingRate
1063- });
1052+ // since it doe not receive minting (so it is considered unallocated).
1053+ // Address(0) will only have non-zero allocation when it is the default target,
1054+ // so we can directly subtract zero address allocation.
1055+ allocation.totalAllocationRate = $.issuancePerBlock - $.allocationTargets[address (0 )].allocatorMintingRate;
1056+ allocation.selfMintingRate = $.totalSelfMintingRate;
1057+ allocation.allocatorMintingRate = allocation.totalAllocationRate - allocation.selfMintingRate;
10641058 }
10651059}
0 commit comments