Lucene search

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

Lottery Insolvency can lead to unclaimable winning tickets despite paying out Frontend and Staking rewards

2023-03-0900:00:00
Code4rena
github.com
7
lottery insolvency
unclaimable tickets
insufficient balance
rewards
vulnerability

Lines of code
<https://github.com/code-423n4/2023-03-wenwin/blob/main/src/LotterySetup.sol#L80&gt;
<https://github.com/code-423n4/2023-03-wenwin/blob/main/src/LotterySetup.sol#L161&gt;
<https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol#L151&gt;

Vulnerability details

Impact

Lottery Insolvency can lead to unclaimable winning tickets despite paying out Frontend and Staking rewards

Proof of Concept

When distributing the winning tokens, it is possible that there is an insufficient balance to be able to pay winning tickets while Frontend and Staking rewards are still claimable.

The call to rewardToken.safeTransfer can revert due to insufficient balance.

    function claimWinningTickets(uint256[] calldata ticketIds) external override returns (uint256 claimedAmount) {
        uint256 totalTickets = ticketIds.length;
        for (uint256 i = 0; i &lt; totalTickets; ++i) {
            claimedAmount += claimWinningTicket(ticketIds[i]);
        }
        rewardToken.safeTransfer(msg.sender, claimedAmount);
    }

This is possible because prizes have a fixed minimum that could lead to insufficient balance to cover the winnings.

Furthermore, Frontend and Staking rewards can be claimed at any time further depleting the available balance to cover winnings.

    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);
    }

The following test demonstrates a contrived example of the lottery going into debt while still paying out rewards -

    function oneRound() private {
        uint128 currentDraw = lottery.currentDraw();

        uint256[] memory ticketIds; //= new uint256[](1);

        vm.startPrank(user);
        ticketIds = _buyTickets(currentDraw, uint120(0x0F), user, user, 1);
        vm.stopPrank();

        // this is TestToken aka DAI
        vm.prank(user);
        lottery.claimRewards(LotteryRewardType.FRONTEND);

        rewardToken.balanceOf(address(lottery));

        vm.prank(user);
        lottery.claimRewards(LotteryRewardType.STAKING);

        // this is DAI
        vm.prank(user);
        staking.getReward();

        uint balance = rewardToken.balanceOf(address(lottery));

        executeDraw();

        lotteryToken.balanceOf(user);

        uint128[] memory drawIds = new uint128[](1);
        drawIds[0] = 0;

        // this is LOT needs current draw executed 
        vm.prank(user);
        referralSystem.claimReferralReward(drawIds);

        (uint price, ) = lottery.claimable(ticketIds[0]);

        if (balance &gt; price) {
          vm.prank(user);
          lottery.claimWinningTickets(ticketIds);
        } else {
          vm.prank(user);
          vm.expectRevert();
          lottery.claimWinningTickets(ticketIds);
        }

        rewardToken.balanceOf(user);
        rewardToken.balanceOf(address(lottery));

        lottery.currentNetProfit();
    }

    function testRekt() public {
        vm.prank(address(lottery));
        stakingToken.mint(user, 1);

        vm.startPrank(user);
          stakingToken.approve(address(staking), 1);
          staking.stake(1);
        vm.stopPrank();

        for (uint i; i &lt; 100; ++i) {
          oneRound();
        }
    }

<https://gist.github.com/alpeware/9dc133b0282cfa662cc0bb13de6dfbfc#file-referralsystembase-sol-L266&gt;

Tools Used

Foundry

Recommended Mitigation Steps

The case of insolvency should be handled specifically and dept payments accordingly.

A few ideas -

  • reward payouts could be paused until the lottery is profitable again
  • prices are variable and always a % of the total balance as opposed to a fix amount
  • pay out in Lottery Token as opposed to Reward Tokens

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

All reactions