Lucene search

K
code423n4Code4renaCODE423N4:2021-12-POOLTOGETHER-FINDINGS-ISSUES-72
HistoryDec 12, 2021 - 12:00 a.m.

claimRewards Does Not Prevent Users From Claiming Rewards After A Promotion's End Epoch

2021-12-1200:00:00
Code4rena
github.com
8

Handle

leastwood

Vulnerability details

Impact

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.

Proof of Concept

The POC consists of a series of steps an attacker must follow in order to exploit this contract:

  • An honest promotion creator calls createPromotion using valid inputs.
  • Lets assume _numberOfEpochs was set to 10 and _tokensPerEpoch was set to 10. Hence 100 tokens was transferred to the TwabRewards contract.
  • The contract also has a balance of 100 tokens from another active promotion.
  • The user holds all the tickets for the promotion just created, meaning when claimRewards is called on a given epoch, the user receives the entire allotted amount which is 10 tokens.
  • 10 epochs pass and the user calls claimRewards on all the unclaimed epochs, receiving a total of 100 tokens in return.
  • The promotion is now inactive and another epoch passes.
  • The user calls claimRewards on epochId of 11.
  • _isClaimedEpoch(_userClaimedEpochs, _epochId) returns false as the user has not claimed this epoch. _calculateRewardAmount proceeds to calculate that another 10 tokens is to be rewarded to the user.
  • As a result, the user will continue to be rewarded with tokens as long as they continue to call claimRewards. This can be done up until epochId = 255.

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.

Tools Used

Manual code review.

Recommended Mitigation Steps

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