leastwood
claimRewards allows a user to collect their TWAB calculated rewards for a provided set of epochIds. The contract utilises a _claimedEpochs mapping which tracks claimed rewards per user. Each claimed epoch is represented by a single bit within a uint256 variable. When an epoch for a promotion has been claimed, the epoch’s allotted bit is updated from 0 to 1. As the numberOfEpochs variable is restricted to a uint8 type, the maximum number of epochs a promotion can have is 255, which fits within the available bits of a uint256 type.
However, if block.timestamp exceeds a promotion’s last epoch, users can continue to call claimRewards on an epochId which exceeds the last epoch. Hence, rewards will continue to be paid out even after a promotion has become inactive.
This severely impacts the correct distribution of rewards between users, potentially leading to a loss of funds for users who don’t actively claim after a promotion has ended.
The POC consists of a series of steps an attacker must follow in order to exploit this contract:
As is outlined, it can be seen that any user could abuse this and continue to claim rewards even though the promotion in which they are a ticket holder of, has ended.
Manual code review.
Consider performing a check in _calculateRewardAmount to ensure _epochEndTimestamp does not exceed the final epoch’s timestamp for a given promotion. This should ensure users cannot unfairly call claimRewards and either steal tokens from other promotions or steal tokens from other users who should be receiving their fair share of the promotion’s reward tokens.
The following code in _calculateRewardAmount can be updated as per below to prevent the discussed issue:
uint256 _epochDuration = _promotion.epochDuration;
uint256 _epochStartTimestamp = _promotion.startTimestamp + (_epochDuration * _epochId);
uint256 _epochEndTimestamp = _epochStartTimestamp + _epochDuration;
uint256 _promotionEndTimestamp = _promotion.startTimestamp +
(_promotion.epochDuration * _promotion.numberOfEpochs);
require(
_promotionEndTimestamp > 0 && _promotionEndTimestamp >= _epochEndTimestamp,
"TwabRewards/promotion-not-active"
);
require(block.timestamp > _epochEndTimestamp, "TwabRewards/epoch-not-over");
The text was updated successfully, but these errors were encountered:
All reactions