Lucene search

K
code423n4Code4renaCODE423N4:2023-03-WENWIN-FINDINGS-ISSUES-338
HistoryMar 09, 2023 - 12:00 a.m.

Loss of reward for stakingRewardRecipient

2023-03-0900:00:00
Code4rena
github.com
6
vulnerability
rewardrecipient
manipulation
ticketssold
claimrewards
lotterymath

Lines of code
<https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol#L249-L257&gt;
<https://github.com/code-423n4/2023-03-wenwin/blob/main/src/LotteryMath.sol#L119-L130&gt;

Vulnerability details

Impact

Anyone can call claimRewards function with rewardType = LotteryRewardType.STAKING, in which function LotteryMath.calculateRewards is used to calculate reward to transfer to beneficiary. By observing number of ticketsSold calculated from the dueTicketsSoldAndReset functionโ€™s two state variableโ€™s - nextTicketId and claimedStakingRewardAtTicketId, such that calculateRewards will return zero. Once such value for ticketsSold is identified, attacker can call claimRewards function with reward type STAKING to update claimedStakingRewardAtTicketId in dueTicketsSoldAndReset function and it will then send zero reward to stakingRewardRecipient.

Attacker can have a script to identify such a value of ticketsSold that will lead to above issue.

#Proof of Concept
<https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol#L249-L257&gt;
<https://github.com/code-423n4/2023-03-wenwin/blob/main/src/LotteryMath.sol#L119-L130&gt;

function claimRewards(LotteryRewardType rewardType) external override returns (uint256 claimedAmount) {
    address beneficiary = (rewardType == LotteryRewardType.FRONTEND) ? msg.sender : stakingRewardRecipient;
    claimedAmount = LotteryMath.calculateRewards(ticketPrice, dueTicketsSoldAndReset(beneficiary), rewardType);

    emit ClaimedRewards(beneficiary, claimedAmount, rewardType);
    rewardToken.safeTransfer(beneficiary, claimedAmount);
}

function dueTicketsSoldAndReset(address beneficiary) private returns (uint256 dueTickets) {
    if (beneficiary == stakingRewardRecipient) {
        dueTickets = nextTicketId - claimedStakingRewardAtTicketId;
        claimedStakingRewardAtTicketId = nextTicketId;
    } else {
        dueTickets = frontendDueTicketSales[beneficiary];
        frontendDueTicketSales[beneficiary] = 0;
    }
}

function calculateRewards(
    uint256 ticketPrice,
    uint256 ticketsSold,
    LotteryRewardType rewardType
)
    internal
    pure
    returns (uint256 dueRewards)
{
    uint256 rewardPercentage = (rewardType == LotteryRewardType.FRONTEND) ? FRONTEND_REWARD : STAKING_REWARD;
    dueRewards = (ticketsSold * ticketPrice).getPercentage(rewardPercentage);
}

Assuming ticket prize is 1.5 Dai.

  1. Attacker will run a script to monitor two state variableโ€™s - nextTicketId and claimedStakingRewardAtTicketId, such that their difference(ticketsSold) if used in calculation of reward in calculateRewards will load to returning zero reward.
    Ex. if such difference tokensSold = 3, then reward calculation will be 3 * 1.5 Dai * 20 / 100 = 0.9 = 0
  2. Once such value for ticketsSold is identified, attacker can call claimRewards to send zero reward to stakingRewardRecipient and then it will update the state variable - claimedStakingRewardAtTicketId in dueTicketsSoldAndReset.
  3. By repeating step 1 and 2, attacker can achieve loss of reward for stakingRewardRecipient.

Tools Used

Manual Review

Recommended Mitigation Steps

We suggest protocol to have some robust mechanism for claimRewards operation for rewardType = Staking.


The text was updated successfully, but these errors were encountered:

All reactions