code-423n4 / 2023-03-wenwin-findings

1 stars 1 forks source link

Griefing attack / risk on claimWinningTickets and claimWinningTicket logic #283

Closed code423n4 closed 1 year ago

code423n4 commented 1 year ago

Lines of code

https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol#L170-L176 https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol#L259-L269

Vulnerability details

Impact

As it stands now, an eventual winner claims the winnings by means of claimWinningTickets function, passing an array of ticketIds. Total winning value is accumulated in claimedAmount variable inside a loop by calling repeatedly claimWinningTicket for each individual ticket.

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

Here is the claimWinningTicket function:

function claimWinningTicket(uint256 ticketId) private onlyTicketOwner(ticketId) returns (uint256 claimedAmount) {
    uint256 winTier;
    (claimedAmount, winTier) = this.claimable(ticketId);
    if (claimedAmount == 0) {
        revert NothingToClaim(ticketId);
    }

    unclaimedCount[ticketsInfo[ticketId].drawId][ticketsInfo[ticketId].combination]--;
    markAsClaimed(ticketId);
    emit ClaimedTicket(msg.sender, ticketId, claimedAmount);
}

As we can see above, if the respective ticket happens to be non claimable, i.e. claimedAmount == 0, the function reverts, and subsequently, the entire transaction fails.

Accordingly, the resulting impact is:

Proof of Concept

The described outcome can occur as a result of the following 2 scenarios:

1. Trading a ticket on the secondary market.

According to the docs, tickets “Can be traded on the secondary market before or after the draw” (ref: https://docs.wenwin.com/wenwin-lottery/the-game/tickets).

2. Simply claiming all the purchased tickets, regardless whether they are winning or not (again, not guaranteed, but very likely).

Tools Used

Recommended Mitigation Steps

In principle, a well-crafted frontend app could mitigate the discussed issue, however, since the Frontend Operators will operate as third parties, it won’t be quite practical to rely on their implementation of the necessary protective measures. Therefore, it’s recommended to perform the required code refactoring in the smart contract itself.

Obviously, it’s up to the development team how exactly to go about this. A possible solution, though, would be to call claimable inside claimWinningTickets rather then in claimWinningTicket, and accordingly build up an array of claimable tickets and another one with non-claimable ones. Then, revert only in case not a single claimable ticket has been found. As long as there is at least one claimable ticket, it should be claimed as intended, subsequently updating the storage and emitting the respective events, for the claimable and non-claimable bunches of tickets respectively.

c4-judge commented 1 year ago

thereksfour marked the issue as unsatisfactory: Invalid