WatchPug
The current implementation of _calculateRewardAmount allows a arbitrary _epochId, which can even be a _epochId > _numberOfEpochs.
A malicious user can call claimRewards() with _epochIds larger than _numberOfEpochs and claim other usersβ rewards.
Furthermore, since claimRewards() will use the wallet balance of promotion.token without limiting the total amount of funds used to be less than or equal to the total rewards added to this promotion.
An attacker can create a fake promotion with the same reward token, the attacker can steal all the funds for other promotions or actually all the ERC20 tokens held by the contract by creating fake promotion with _numberOfEpochs set to 0. The attacker may or may not use a fake ticket.
function _calculateRewardAmount(
address _user,
Promotion memory _promotion,
uint256 _epochId
) internal view returns (uint256) {
uint256 _epochDuration = _promotion.epochDuration;
uint256 _epochStartTimestamp = _promotion.startTimestamp + (_epochDuration * _epochId);
uint256 _epochEndTimestamp = _epochStartTimestamp + _epochDuration;
require(block.timestamp > _epochEndTimestamp, "TwabRewards/epoch-not-over");
ITicket _ticket = ITicket(_promotion.ticket);
uint256 _averageBalance = _ticket.getAverageBalanceBetween(
_user,
uint64(_epochStartTimestamp),
uint64(_epochEndTimestamp)
);
uint64[] memory _epochStartTimestamps = new uint64[](1);
_epochStartTimestamps[0] = uint64(_epochStartTimestamp);
uint64[] memory _epochEndTimestamps = new uint64[](1);
_epochEndTimestamps[0] = uint64(_epochEndTimestamp);
uint256[] memory _averageTotalSupplies = _ticket.getAverageTotalSuppliesBetween(
_epochStartTimestamps,
_epochEndTimestamps
);
if (_averageTotalSupplies[0] > 0) {
return (_promotion.tokensPerEpoch * _averageBalance) / _averageTotalSupplies[0];
}
return 0;
}
function claimRewards(
address _user,
uint256 _promotionId,
uint256[] calldata _epochIds
) external override returns (uint256) {
Promotion memory _promotion = _getPromotion(_promotionId);
uint256 _rewardsAmount;
uint256 _userClaimedEpochs = _claimedEpochs[_promotionId][_user];
for (uint256 index = 0; index < _epochIds.length; index++) {
uint256 _epochId = _epochIds[index];
require(
!_isClaimedEpoch(_userClaimedEpochs, _epochId),
"TwabRewards/rewards-already-claimed"
);
_rewardsAmount += _calculateRewardAmount(_user, _promotion, _epochId);
_userClaimedEpochs = _updateClaimedEpoch(_userClaimedEpochs, _epochId);
}
_claimedEpochs[_promotionId][_user] = _userClaimedEpochs;
_promotion.token.safeTransfer(_user, _rewardsAmount);
emit RewardsClaimed(_promotionId, _epochIds, _user, _rewardsAmount);
return _rewardsAmount;
}
Given:
Change to:
function claimRewards(
address _user,
uint256 _promotionId,
uint256[] calldata _epochIds
) external override returns (uint256) {
Promotion memory _promotion = _getPromotion(_promotionId);
uint256 _rewardsAmount;
uint256 _userClaimedEpochs = _claimedEpochs[_promotionId][_user];
uint256 _numberOfEpochs = _promotions[_promotionId].numberOfEpochs;
for (uint256 index = 0; index < _epochIds.length; index++) {
uint256 _epochId = _epochIds[index];
require(_epochId < _numberOfEpochs, "!_epochId");
require(
!_isClaimedEpoch(_userClaimedEpochs, _epochId),
"TwabRewards/rewards-already-claimed"
);
_rewardsAmount += _calculateRewardAmount(_user, _promotion, _epochId);
_userClaimedEpochs = _updateClaimedEpoch(_userClaimedEpochs, _epochId);
}
_claimedEpochs[_promotionId][_user] = _userClaimedEpochs;
_promotion.token.safeTransfer(_user, _rewardsAmount);
emit RewardsClaimed(_promotionId, _epochIds, _user, _rewardsAmount);
return _rewardsAmount;
}
The text was updated successfully, but these errors were encountered:
All reactions