Wenwin contest - bshramin's results

The next generation of chance-based gaming.

General Information

Platform: Code4rena

Start Date: 06/03/2023

Pot Size: $36,500 USDC

Total HM: 8

Participants: 93

Period: 3 days

Judge: cccz

Total Solo HM: 3

Id: 218

League: ETH

Wenwin

Findings Distribution

Researcher Performance

Rank: 77/93

Findings: 1

Award: $21.70

QA:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

21.7018 USDC - $21.70

Labels

bug
grade-b
QA (Quality Assurance)
sponsor disputed
Q-43

External Links

Title

Draws can only be executed sequentially

https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol#L133-L142

Impact

  • Excess calls to the Oracle
  • Excess gas consumption
  • The probability of no one wanting to call an execution:
  • If no one uses the contract for some amount of time it will become less and less interesting to use, making the comeback impossible after some time.

Proof of Concept

The following test shows the problem in action.

function testSequentialExecution() public {
    vm.startPrank(USER);
    rewardToken.mint(2*TICKET_PRICE);
    rewardToken.approve(address(lottery), 2*TICKET_PRICE);
    uint256 pastDrawWinningTicketId = buyTicket(lottery.currentDraw(), uint120(0xF0), address(0));
    uint256 currentDrawWinningTicketId = buyTicket(lottery.currentDraw()+5, uint120(0xF0), address(0));
    vm.stopPrank();

    vm.warp(block.timestamp + PERIOD * 7);

    uint256 randomNumber = 0xF0;
    lottery.executeDraw();
    vm.prank(randomNumberSource);
    lottery.onRandomNumberFulfilled(randomNumber);

    vm.startPrank(USER);
    claimTicket(pastDrawWinningTicketId); // Succeeds
    vm.expectRevert(abi.encodeWithSelector(NothingToClaim.selector, currentDrawWinningTicketId));
    claimTicket(currentDrawWinningTicketId); // Fails but could succeed if the right execution logic was implemented
    vm.stopPrank();


    for (uint i; i< 6;++i) {
        lottery.executeDraw();
        vm.prank(randomNumberSource);
        lottery.onRandomNumberFulfilled(randomNumber);
    }

    vm.prank(USER);
    claimTicket(currentDrawWinningTicketId); // Succeeds
}
image

Tools Used

Manual Review

Some possible solutions:

  • Execute draw can execute all the draws that can be executed, instead of just the next one
  • Execute draw can take a drawId and only execute that particular draw
  • We can at least mitigate this problem by returning the correct error message from function claimable when the draw is not executed yet. This way the user will know that the draw is not executed yet and will not try to claim the ticket.

#0 - thereksfour

2023-03-12T12:10:54Z

1 INFO DOWN: 2 LOW

#1 - c4-judge

2023-03-12T12:10:58Z

thereksfour marked the issue as grade-b

#2 - c4-sponsor

2023-03-14T11:28:42Z

0xluckydev marked the issue as sponsor disputed

#3 - 0xluckydev

2023-03-14T11:29:26Z

Design decision. Plus, someone will always execute a draw.

#4 - thereksfour

2023-03-17T12:53:17Z

2L
B

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter