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: 52/93
Findings: 2
Award: $34.42
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
Number | Issues Details | Instances |
---|---|---|
[L-1] | Using _safeMint() instead of _mint() | 1 |
[L-2] | Owner can renounce Ownership | - |
[L-3] | Lack of zero address checks | 2 |
_safeMint()
instead of _mint()
For the ERC721
contract, _mint()
won't check if the recipient is able to receive the NFT. If an incorrect address is passed, it will result in a silent failure and loss of asset.
OpenZeppelin recommendation is to use the safe variant of _mint()
.
Instances(1):
File: src/Ticket.sol 26: _mint(to, ticketId);
https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Ticket.sol#L26
Replace _mint()
with _safeMint()
.
Renouncing ownership results in the contract being left without an owner, removing any functionality that is only available to the owner.
We recommend either reimplementing the function to disable it or clearly stating whether it is part of the contract design.
If these variable get configured with address zero, failure to immediately reset the value can result in unexpected behavior for the project.
Instances(2):
File: src/staking/StakedTokenLock.sol 18: stakedToken = IStaking(_stakedToken);
https://github.com/code-423n4/2023-03-wenwin/blob/main/src/staking/StakedTokenLock.sol#L18
File: src/RNSourceBase.sol 12: authorizedConsumer = _authorizedConsumer;
https://github.com/code-423n4/2023-03-wenwin/blob/main/src/RNSourceBase.sol#L12
Add zero address checks.
#0 - thereksfour
2023-03-12T10:21:51Z
1 L 2 INFO
#1 - c4-judge
2023-03-12T10:21:54Z
thereksfour marked the issue as grade-b
#2 - c4-sponsor
2023-03-14T11:11:46Z
0xluckydev marked the issue as sponsor disputed
#3 - thereksfour
2023-03-17T13:34:05Z
1 L 2 INFO B
🌟 Selected for report: Rolezn
Also found by: 0x1f8b, 0x6980, 0xSmartContract, 0xhacksmithh, 0xnev, Haipls, Inspectah, JCN, LethL, Madalad, MiniGlome, Pheonix, Rageur, RaymondFam, ReyAdmirado, SAAJ, Sathish9098, adriro, air, arialblack14, atharvasama, c3phas, ch0bu, ddimitrov22, descharre, hunter_w3b, igingu, matrix_0wl, rokso, saneryee, schrodinger, slvDev, volodya, yongskiws
12.7206 USDC - $12.72
Issue | Instances | Total Gas Saved | |
---|---|---|---|
GAS-1 | Use assembly to check for address(0) | 10 | 60 |
GAS-2 | ++i costs less gas than i++ , especially when it's used in for loops (--i /i-- too) | 7 | - |
GAS-3 | <x> += <y> Costs More Gas Than <x> = <x> + <y> For State Variables(<x> -= <y> too) | 3 | 339 |
GAS-4 | ++i /i++ should be unchecked{++i} /unchecked{i++} when it is not possible for them to overflow, as is the case when used in for - and while -loops | 11 | - |
GAS-5 | Constructors can be marked payable | 10 | - |
GAS-6 | Structs can be packed into fewer storage slots | 3 | 60000 |
GAS-7 | Using storage instead of memory for structs/arrays saves gas | 2 | 4200 |
GAS-8 | Using custom error instead of assert | 2 | - |
GAS-9 | Perform all operations before emitting event | 1 | - |
Total: 49 instances over 9 issues with 64,599 gas saved
address(0)
Saves 6 gas per instance
Instances(10):
File: src/LotterySetup.sol 42: if (address(lotterySetupParams.token) == address(0)) {{
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/LotterySetup.sol
File: src/ReferralSystem.sol 60: if (referrer != address(0)) {
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/ReferralSystem.sol
File: src/RNSourceController.sol 78: if (address(rnSource) == address(0)) { 81: if (address(source) != address(0)) { 90: if (address(newSource) == address(0)) {
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/RNSourceController.sol
File: src/staking/Staking.sol 31: if (address(_lottery) == address(0)) { 34: if (address(_rewardsToken) == address(0)) { 37: if (address(_stakingToken) == address(0)) { 109: if (from != address(0)) { 113: if (to != address(0)) {
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/staking/Staking.sol
++i
costs less gas than i++
, especially when it's used in for
loops (--i
/i--
too)Saves 5 gas per loop
Instances(7):
File: src/Lottery.sol 193: unclaimedCount[drawId][ticket]++; 194: ticketsSold[drawId]++; 205: uint128 drawFinalized = currentDraw++; 266: unclaimedCount[ticketsInfo[ticketId].drawId][ticketsInfo[ticketId].combination]--;
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol
File: src/Ticket.sol 24: ticketId = nextTicketId++;
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Ticket.sol
File: src/TicketUtils.sol 60: currentSelectionCount--; 70: currentNumber++;
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/TicketUtils.sol
<x> += <y>
Costs More Gas Than <x> = <x> + <y>
For State Variables(<x> -= <y>
too)Saves 113 gas per instance
Instances(3):
File: src/Lottery.sol 275: currentNetProfit += int256(unclaimedJackpotTickets * winAmount[drawId][selectionSize]);
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol
File: src/staking/StakedTokenLock.sol 30: depositedBalance += amount; 43: depositedBalance -= amount;
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/staking/StakedTokenLock.sol
++i
/i++
should be unchecked{++i}
/unchecked{i++}
when it is not possible for them to overflow, as is the case when used in for
- and while
-loopsSaves 30-40 gas per loop. Source
Instances(11):
File: src/Lottery.sol 125: for (uint256 i = 0; i < drawIds.length; ++i) { 127: for (uint256 i = 0; i < totalTickets; ++i) { 211: for (uint8 winTier = 1; winTier < selectionSize; ++winTier) {
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol
File: src/LotterySetup.sol 169: for (uint8 winTier = 1; winTier < selectionSize; ++winTier) {
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/LotterySetup.sol
File: src/ReferralSystem.sol 35: for (uint256 i = 0; i < _rewardsToReferrersPerDraw.length; ++i) { 77: for (uint256 counter = 0; counter < drawIds.length; ++counter) {
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/ReferralSystem.sol
File: src/TicketUtils.sol 28: for (uint8 i = 0; i < selectionMax; ++i) { 57: for (uint256 i = 0; i < selectionSize; ++i) { 65: for (uint256 i = 0; i < selectionSize; ++i) { 68: for (uint256 j = 0; j <= currentNumber; ++j) { 95: for (uint8 i = 0; i < selectionMax; ++i) {
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/TicketUtils.sol
payable
Payable functions cost less gas to execute, since the compiler does not have to add extra checks to ensure that a payment wasn't provided. A constructor can safely be marked as payable, since only the deployer would be able to pass funds.
Instances(10): src/Lottery.sol src/LotterySetup.sol src/LotteryToken.sol src/ReferralSystem.sol src/RNSourceBase.sol src/RNSourceController.sol src/Ticket.sol src/VRFv2RNSource.sol src/staking/StakedTokenLock.sol src/staking/Staking.sol
Each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct. Subsequent reads as well as writes have smaller gas savings Instances(3):
File: src/interfaces/ILotterySetup.sol 53: struct LotteryDrawSchedule { 54: /// @dev First draw is scheduled to take place at this timestamp 55: uint256 firstDrawScheduledAt; 56: /// @dev Period for running lottery 57: uint256 drawPeriod; 58: /// @dev Cooldown period when users cannot register tickets for draw anymore 59: uint256 drawCoolDownPeriod; 60: }
File: src/interfaces/ILotterySetup.sol 63: struct LotterySetupParams { 64: /// @dev Token to be used as reward token for the lottery 65: IERC20 token; 66: /// @dev Parameters of the draw schedule for the lottery 67: LotteryDrawSchedule drawSchedule; 68: /// @dev Price to pay for playing single game (including fee) 69: uint256 ticketPrice; 70 : /// @dev Count of numbers user picks for the ticket 71: uint8 selectionSize; 72: /// @dev Max number user can pick 73: uint8 selectionMax; 74: /// @dev Expected payout for one ticket, expressed in `rewardToken` 75: uint256 expectedPayout; 76: /// @dev Array of fixed rewards per each non jackpot win 77: uint256[] fixedRewards; 78: }
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/interfaces/ILotterySetup.sol
File: src/interfaces/IReferralSystemDynamic.sol 24: struct MinimumReferralsRequirement { 25: uint256 minimumTicketsSold; 26: uint256 factor; 27: ReferralRequirementFactorType factorType; 28: }
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/interfaces/IReferralSystemDynamic.sol
storage
instead of memory
for structs/arrays saves gasUse the storage
instead of memory
when declaring local variables to minimize gas consumption when fetching data from storage. Assigning the data to a memory variable reads all fields of the struct/array from storage, which incurs a 2100 gas for each field.
Instances(2):
File: src/Lottery.sol 160: TicketInfo memory ticketInfo = ticketsInfo[ticketId];
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol
File: src/ReferralSystem.sol 139: UnclaimedTicketsData memory _unclaimedTickets = unclaimedTickets[drawId][msg.sender];
https://github.com/code-423n4/2023-03-wenwin/blob/main/src/ReferralSystem.sol
When the condition is false, assert
tends to consume all remaining gas and undo all changes made.
But custom error refunds all remaining gas fees we offered to pay above and beyond, reverting all changes.
Instances(2):
File: src/LotterySetup.sol 147: assert(initialPot > 0);
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/LotterySetup.sol
File: src/TicketUtils.sol 99: assert((winTier <= selectionSize) && (intersection == uint256(0)));
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/TicketUtils.sol
The following event performs some kind of storage access. However, should any of the activities coming after it reverts, the storage access will have been redundant. In some cases this equates to a wasted Gwarmaccess, costing 2100 gas per storage slot
Instances(1):
File: src/Lottery.sol 151: function claimRewards(LotteryRewardType rewardType) external override returns (uint256 claimedAmount) { 152: address beneficiary = (rewardType == LotteryRewardType.FRONTEND) ? msg.sender : stakingRewardRecipient; 153: claimedAmount = LotteryMath.calculateRewards(ticketPrice, dueTicketsSoldAndReset(beneficiary), rewardType); 154: 155: emit ClaimedRewards(beneficiary, claimedAmount, rewardType); 156: rewardToken.safeTransfer(beneficiary, claimedAmount); 157: }
Link to code: https://github.com/code-423n4/2023-03-wenwin/blob/main/src/Lottery.sol
#0 - c4-judge
2023-03-12T14:30:40Z
thereksfour marked the issue as grade-b
#1 - c4-sponsor
2023-03-14T13:12:29Z
TutaRicky marked the issue as sponsor acknowledged