@@ -107,16 +107,21 @@ contract KnowledgeCollection is INamed, IVersioned, ContractStatus, IInitializab
107107 merkleRoot,
108108 knowledgeAssetsAmount,
109109 byteSize,
110- currentEpoch + 1 ,
111- currentEpoch + epochs + 1 ,
110+ currentEpoch,
111+ currentEpoch + epochs,
112112 tokenAmount,
113113 isImmutable
114114 );
115115
116+ // Validate that the provided token amount is sufficient
117+ // include current epoch is false and user pays for number of epochs
116118 _validateTokenAmount (byteSize, epochs, tokenAmount, true );
117119
118- es.addTokensToEpochRange (1 , currentEpoch, currentEpoch + epochs + 1 , tokenAmount);
119- es.addEpochProducedKnowledgeValue (publisherNodeIdentityId, currentEpoch, tokenAmount);
120+ // Distribute time-weight tokenAmount across current, full, and final fractional epochs
121+ _distributeTokens (tokenAmount, epochs, currentEpoch);
122+
123+ // Record the knowledge value produced by the publisher in the current epoch
124+ epochStorage.addEpochProducedKnowledgeValue (publisherNodeIdentityId, currentEpoch, tokenAmount);
120125
121126 _addTokens (tokenAmount, paymaster);
122127
@@ -323,4 +328,55 @@ contract KnowledgeCollection is INamed, IVersioned, ContractStatus, IInitializab
323328 }
324329 }
325330 }
331+
332+ /**
333+ * @dev Distributes tokens across epochs using time-weighted allocation
334+ * Allocates tokens proportionally based on time remaining in current epoch and distributes
335+ * the remainder across full epochs and final fractional epoch
336+ * @param tokenAmount Total amount of tokens to distribute across epochs
337+ * @param epochs Number of epochs to distribute tokens over
338+ * @param currentEpoch The current epoch number where distribution starts
339+ */
340+ function _distributeTokens (uint96 tokenAmount , uint256 epochs , uint40 currentEpoch ) internal {
341+ require (epochs > 0 , "epochs must be > 0 " );
342+
343+ uint256 epochLengthInSeconds = chronos.epochLength ();
344+ uint256 timeRemainingInCurrentEpoch = chronos.timeUntilNextEpoch (); // seconds remaining in current epoch
345+ uint256 baseTokensPerFullEpoch = tokenAmount / epochs; // nominal amount for a full epoch
346+ uint256 currentEpochAllocation = (baseTokensPerFullEpoch * timeRemainingInCurrentEpoch) / epochLengthInSeconds;
347+ uint256 finalEpochAllocation = baseTokensPerFullEpoch - currentEpochAllocation; // goes to the final fractional epoch
348+ uint256 numberOfFullEpochs = epochs - 1 ; // number of full middle epochs
349+ uint256 totalTokensForFullEpochs = baseTokensPerFullEpoch * numberOfFullEpochs;
350+
351+ // Add any rounding remainder to the final epoch so total == tokenAmount
352+ uint256 totalAllocated = currentEpochAllocation + totalTokensForFullEpochs + finalEpochAllocation;
353+ if (totalAllocated < tokenAmount) {
354+ finalEpochAllocation += tokenAmount - totalAllocated;
355+ }
356+
357+ // 1) Current (fractional) epoch
358+ if (currentEpochAllocation > 0 ) {
359+ epochStorage.addTokensToEpochRange (1 , currentEpoch, currentEpoch, uint96 (currentEpochAllocation));
360+ }
361+
362+ // 2) Full epochs between current and final
363+ if (numberOfFullEpochs > 0 && totalTokensForFullEpochs > 0 ) {
364+ epochStorage.addTokensToEpochRange (
365+ 1 ,
366+ currentEpoch + 1 ,
367+ currentEpoch + uint40 (numberOfFullEpochs),
368+ uint96 (totalTokensForFullEpochs)
369+ );
370+ }
371+
372+ // 3) Final (fractional) epoch
373+ if (finalEpochAllocation > 0 ) {
374+ epochStorage.addTokensToEpochRange (
375+ 1 ,
376+ currentEpoch + uint40 (epochs),
377+ currentEpoch + uint40 (epochs),
378+ uint96 (finalEpochAllocation)
379+ );
380+ }
381+ }
326382}
0 commit comments