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: 10/93
Findings: 2
Award: $641.04
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Cyfrin
Also found by: Yukti_Chinta, adriro, anodaram, auditor0517, bin2chen, gogo, minhtrng
619.3361 USDC - $619.34
calculateNewProfit() Missing divide by PERCENTAGE_BASE, resulting in less drawRewardSize() and fewer winTier rewards
calculateNewProfit() is used to recalculate the profit, the code is as follows:
function calculateNewProfit( int256 oldProfit, uint256 ticketsSold, uint256 ticketPrice, bool jackpotWon, uint256 fixedJackpotSize, uint256 expectedPayout ) internal pure returns (int256 newProfit) { uint256 ticketsSalesToPot = (ticketsSold * ticketPrice).getPercentage(TICKET_PRICE_TO_POT); newProfit = oldProfit + int256(ticketsSalesToPot); uint256 expectedRewardsOut = jackpotWon ? calculateReward(oldProfit, fixedJackpotSize, fixedJackpotSize, ticketsSold, true, expectedPayout) : calculateMultiplier(calculateExcessPot(oldProfit, fixedJackpotSize), ticketsSold, expectedPayout) //<-----this place miss div PERCENTAGE_BASE * ticketsSold * expectedPayout; newProfit -= int256(expectedRewardsOut); }
expectedRewardsOut
is calculated if not a Jackpot, using the following formula calculateMultiplier(calculateExcessPot(oldProfit, fixedJackpotSize), ticketsSold, expectedPayout) * ticketsSold * expectedPayout
calculateMultiplier() returns a precision of 100_000, the above formula is missing the division by PERCENTAGE_BASE, the normal should be to use PercentageMath.getPercentage(calculateMultiplier(calculateExcessPot(oldProfit, fixedJackpotSize), ticketsSold, expectedPayout), ticketsSold * expectedPayout)
This method is used to calculate currentNetProfit
, if the number incorrect (negative) resulting in less drawRewardSize() and fewer winTier rewards
function calculateNewProfit( int256 oldProfit, uint256 ticketsSold, uint256 ticketPrice, bool jackpotWon, uint256 fixedJackpotSize, uint256 expectedPayout ) internal pure returns (int256 newProfit) { uint256 ticketsSalesToPot = (ticketsSold * ticketPrice).getPercentage(TICKET_PRICE_TO_POT); newProfit = oldProfit + int256(ticketsSalesToPot); uint256 expectedRewardsOut = jackpotWon ? calculateReward(oldProfit, fixedJackpotSize, fixedJackpotSize, ticketsSold, true, expectedPayout) - : calculateMultiplier(calculateExcessPot(oldProfit, fixedJackpotSize), ticketsSold, expectedPayout) - * ticketsSold * expectedPayout; + :PercentageMath.getPercentage(calculateMultiplier(calculateExcessPot(oldProfit, fixedJackpotSize),ticketsSold, expectedPayout), + ticketsSold * expectedPayout)` newProfit -= int256(expectedRewardsOut); }
#0 - c4-judge
2023-03-11T13:22:19Z
thereksfour marked the issue as duplicate of #219
#1 - c4-judge
2023-03-19T10:23:44Z
thereksfour marked the issue as satisfactory
#2 - c4-judge
2023-03-21T02:20:49Z
thereksfour changed the severity to 3 (High Risk)
🌟 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
21.7018 USDC - $21.70
L-01: Ticket.mint() is recommended to use _safeMint() to avoid the receiver is contract, can not properly handle the NFT, resulting in the loss of NFT
function mint(address to, uint128 drawId, uint120 combination) internal returns (uint256 ticketId) { ticketId = nextTicketId++; ticketsInfo[ticketId] = TicketInfo(drawId, combination, false); - _mint(to, ticketId); + _safeMint(to, ticketId); }
L-02:claimable() It is more reasonable to use drawScheduledAt to determine the expiration, currently using ticketRegistrationDeadline () will be less drawCoolDownPeriod, but this is the purchase of cooldown, should not be counted as a draw cycle
function claimable(uint256 ticketId) external view override returns (uint256 claimableAmount, uint8 winTier) { TicketInfo memory ticketInfo = ticketsInfo[ticketId]; if (!ticketInfo.claimed) { uint120 _winningTicket = winningTicket[ticketInfo.drawId]; winTier = TicketUtils.ticketWinTier(ticketInfo.combination, _winningTicket, selectionSize, selectionMax); - if (block.timestamp <= ticketRegistrationDeadline(ticketInfo.drawId + LotteryMath.DRAWS_PER_YEAR)) { + if (block.timestamp <= drawScheduledAt(ticketInfo.drawId + LotteryMath.DRAWS_PER_YEAR)) { claimableAmount = winAmount[ticketInfo.drawId][winTier]; } } }
#0 - thereksfour
2023-03-12T10:05:57Z
1 L DOWN: 1 L
#1 - c4-judge
2023-03-12T10:06:04Z
thereksfour marked the issue as grade-b
#2 - c4-sponsor
2023-03-14T10:59:16Z
0xluckydev marked the issue as sponsor disputed
#3 - 0xluckydev
2023-03-14T10:59:33Z
Design decision
#4 - thereksfour
2023-03-17T12:40:22Z