Incorrect Reward Rate Selection in Cross-Halving Distributions Causes Token Underemission
After the IMM-HIGH-01 fix, rewardDistribution uses getBlockReward(nextStep) (the rate at the END of each segment) instead of the rate at the start. Distributions crossing a halving boundary still emit ~33% fewer tokens than expected.
Description
In the rewardDistribution() function of the MATToken contract, the reward rate is incorrectly used when distribution crosses a halving boundary. This issue was introduced by the following pull request: https://github.com/matchain/contracts/pull/10, which was initially intended to address the IMM-HIGH-1 finding.
Specifically, the error is in these lines:
uint256 rewardAmount = getBlockReward(nextStep) * (nextStep - _lastRewardDistributionBlock);
And inside the while loop:
rewardAmount += getBlockReward(nextStep) * (nextStep - oldNextStep);
The problem is as follows:
- In
getBlockReward(nextStep), the reward rate of the next halving period is applied to all blocks in the current segment. nextStepis the end of the segment, which in many cases already belongs to the next halving period with a reduced rate.- It should use the rate from the beginning of the segment (
_lastRewardDistributionBlockoroldNextStep). - Since the reward rate decreases in each halving period, using the rate from the next period results in a lower emission than it should be.
The reward rate is constant within a single halving period but decreases by half between different halving periods. The problem occurs because the code uses the reward rate from the end of each segment (nextStep) for the entire segment calculation. When nextStep is the halving block or later, getBlockReward(nextStep) returns the reduced post-halving rate. This reduced rate is then incorrectly applied to all blocks in the segment, including those that occurred before the halving and should have received the higher rate.
Impact
A test simulating cross-halving distribution measured a 33% underemission compared to the expected average rate.
CASE 2: Distribution crossing halvingBlocks processed: 100Tokens emitted: 8Emission rate per block (scaled): 83 * 10^-3ANALYSIS OF THE BUG:Expected average rate (scaled): 124 * 10^-3Expected emission crossing halving: 12Actual emission crossing halving: 8Difference of emission: 4Percentage of underemission: 33 %
For a cross-halving period processing 100 blocks (50 before halving + 50 after), the emission rate matches Case 3 (post-halving) instead of being the average between Case 1 (pre-halving) and Case 3, resulting in 33% fewer tokens than the economic model intends.
Recommendation
Modify the rewardDistribution() function to use the reward rate from the beginning of each segment:
// For the first segmentuint256 rewardAmount = getBlockReward(_lastRewardDistributionBlock) * (nextStep - _lastRewardDistributionBlock);// For subsequent segments in the while looprewardAmount += getBlockReward(oldNextStep) * (nextStep - oldNextStep);

