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: 22/93
Findings: 2
Award: $318.44
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: sashik_eth
Also found by: 0xbepresent, Dug, Haipls, MadWookie, adriro, hl_, horsefacts, peanuts
148.6407 USDC - $148.64
https://github.com/code-423n4/2023-03-wenwin/blob/91b89482aaedf8b8feb73c771d11c257eed997e8/src/Lottery.sol#L259-L269 https://github.com/code-423n4/2023-03-wenwin/blob/91b89482aaedf8b8feb73c771d11c257eed997e8/src/Lottery.sol#L170-L176
Secondary NFT buyer can lose funds if prize was already claimed before ownership.
Function claimWinningTickets
of Lottery.sol
allows players to claim their prize if their ticket is the winning ticket. After the prize is claimed, the ticket will be marked as claimed to prevent double claiming.
File: 2023-03-wenwin/src/Lottery.sol (L170 - 176) 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); }
File: 2023-03-wenwin/src/Lottery.sol (L259 - 269) 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); }
Given that the NFT is tradable in a secondary market, it is reasonable to assume that a buyer would want to buy the NFT if it has value (i.e. eligible for a prize).
Consider a case where:
claimWinningTickets
function.Manual review.
Disable NFT transfer/ burn the NFT once the prize is claimed.
#0 - c4-judge
2023-03-11T11:16:25Z
thereksfour marked the issue as duplicate of #425
#1 - c4-judge
2023-03-19T10:06:24Z
thereksfour marked the issue as satisfactory
🌟 Selected for report: adriro
Also found by: 0x1f8b, 0xAgro, 0xSmartContract, 0xfuje, 0xkazim, 0xnev, Aymen0909, Bason, Cyfrin, DadeKuma, LethL, Madalad, MohammedRizwan, Rolezn, SAAJ, SunSec, Udsen, Yukti_Chinta, ast3ros, bin2chen, brgltd, bshramin, btk, bugradar, catellatech, cryptostellar5, descharre, dontonka, erictee, fatherOfBlocks, georgits, glcanvas, hl_, horsefacts, igingu, juancito, lukris02, martin, nadin, nomoi, peanuts, pipoca, sakshamguruji, seeu, slvDev, tnevler, zaskoh
169.7989 USDC - $169.80
safemint()
should be used rather than mint()
First draw schedule
to ensure time not set in the pastLotterySetupParams
constructorThe test coverage rate of the project is 80.69%. Testing all functions is best practice in terms of security criteria.
In complex projects such as Defi, the interpretation of all functions and their arguments and returns is important for code readability and auditability.
It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation.
Missing first line of code
For example:
File: 2023-03-wenwin/src/Lottery.sol 1: // SPDX-License-Identifier: UNLICENSED
The complier version used in the contract is 0.8.19 which is very new and hence not yet thoroughly tested for possible bugs, etc. It is best practice to use a more thoroughly tested complier version e.g. 0.8.16
For example:
File: 2023-03-wenwin/src/Lottery.sol 3: pragma solidity 0.8.19;
It is best practice for exact functions to be imported.
For example:
File: 2023-03-wenwin/src/Lottery.sol 5: import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6: import "@openzeppelin/contracts/utils/math/Math.sol"; 7: import "src/ReferralSystem.sol"; 8: import "src/RNSourceController.sol"; 9: import "src/staking/Staking.sol"; 10: import "src/LotterySetup.sol"; 11: import "src/TicketUtils.sol";
safemint()
should be used rather than mint()
mint()
is discouraged in favor of safeMint()
which ensures that the recipient is either an EOA or implements IERC721Receiver. Both OpenZeppelin and solmate have versions of this function.
File: 2023-03-wenwin/src/Lottery.sol 192: ticketId = mint(msg.sender, drawId, ticket);
First draw schedule
to ensure time not set in the pastPer the constructor of LotterySetup.sol
, protocol owner sets the first draw schedule:
File: 2023-03-wenwin/src/LotterySetup.sol 83: firstDrawSchedule = lotterySetupParams.drawSchedule.firstDrawScheduledAt;
There should be a check for firstDrawSchedule
> block.timestamp
to ensure that firstDrawSchedule
is not set in the past by mistake.
LotterySetupParams
constructorIt is best practice for every variable to be checked for 0 value in case of a typo error.
File: 2023-03-wenwin/src/LotterySetup.sol 86: ticketPrice = lotterySetupParams.ticketPrice; 87: selectionSize = lotterySetupParams.selectionSize; 88: selectionMax = lotterySetupParams.selectionMax; 89: expectedPayout = lotterySetupParams.expectedPayout;
File: 2023-03-wenwin/src/ReferralSystem.sol 122: // 0.75% 123: return totalTicketsSoldPrevDraw.getPercentage(PercentageMath.ONE_PERCENT * 75 / 100); 124: } 125: if (totalTicketsSoldPrevDraw < 1_000_000) { 126: // 0.5% 127: return totalTicketsSoldPrevDraw.getPercentage(PercentageMath.ONE_PERCENT * 50 / 100); 128: }
Write as the following instead:
totalTicketsSoldPrevDraw.getPercentage(750)
totalTicketsSoldPrevDraw.getPercentage(500)
The contract should be capable to take a large purchase from whales without reverting due to excessive gas.
The buyTickets()
function in Lottery.sol
loops through the drawId
length, which refers to the number of tickets that a player wants to buy.
If drawIds.length
is too high due to the same player buying a massive number of tickets at one go, the function will revert due to reaching maximum gas limit. Consider breaking up the loop into smaller loops so that each execution can be successful.
File: 2023-03-wenwin/src/Lottery.sol 124: ticketIds = new uint256[](tickets.length); 125: for (uint256 i = 0; i < drawIds.length; ++i) { ticketIds[i] = registerTicket(drawIds[i], 126: tickets[i], frontend, referrer); 127: }
#0 - thereksfour
2023-03-12T10:42:36Z
3 L 5INFO
#1 - c4-judge
2023-03-12T10:42:43Z
thereksfour marked the issue as grade-a
#2 - c4-sponsor
2023-03-14T11:18:19Z
TutaRicky marked the issue as sponsor disputed
#3 - thereksfour
2023-03-17T13:30:18Z
3L 5INFO A