Guardian's review of Seasons for Zap, published December 2024. The report records 20 findings across 2 review rounds, including 2 high and 1 medium.
- Published
- Review window
- November 29 to December 9, 2024
- Rounds
- Main Review, Remediation Review
- Language
- Solidity
- Chains
- Base
- Sector
- Token launches, Gaming and prediction
- 0 Critical
- 2 High
- 1 Medium
- 17 Low
- 0 Informational
Scope
Findings 20
Main Review
19 findings · December 3 to 9, 2024-
H-01 High Cumulative Tickets Can Be Inaccurate Logical Error Resolved
Description
In the
uploadEntrantsfunction if the first user in the providedusersarray is being processed then the previousTickets amount used to write to thecumulativeTicketsarray is 0. This will be inaccurate if theuploadEntrantsfunction is called multiple times, which it is likely to be in the event that a large number of entrants must be uploaded. Additionally when processing subsequent users thecumulativeTicketsvalue is read using the current iterationindex - 1, which would also yield an inaccuratepreviousTicketsvalue when theuploadEntrantsfunction is called multiple times. This will perturb the ability for users with higher ticket IDs to win the season as the random value is modulo by atotalTicketsamount which will be smaller than the actual total tickets.Recommendation
Instead of basing the
previousTicketson the current users array index, base it off of the length of thecumulativeTicketsarray length + the current user array index. -
H-02 High No validation winners claiming prizes multiple times Resolved
Description
A winner should only be able to claim once. I have added a patch in the linked commit
-
M-01 Medium More Than amountOfWinners May Be Selected Gaming Resolved
Description
**The **
selectWinnerfunction implements a validation against a winner being selected when the desiredamountOfWinnershas already been met, however since the winner is not pushed to the winners array until fulfillment of the random request the admin could intentionally or accidentally call theselectWinnerfunction more than theamountOfWinnersnumber of times and allow the system to accrue more than theamountOfWinnersin thewinnersarray.Recommendation
Consider also including the
winnerslength validation in the_fulfillRandomnessfunction. -
L-01 Low Lacking SafeTransfer Usage Best Practices Resolved
Description
In the
initSeasonfunction the price ERC20 tokens are transferred in usingtransferFromwithout validating the return value. InsteadsafeTransfershould be used to safely handle any arbitrary price tokens. Additionally in theclaimPrizesfunction transfer is used without checking the return value when sending ERC20 tokens to the winner.Recommendation
Use
safeTransferFromfor the transferring of ERC20 prize tokens in theinitSeasonfunction and safeTransfer when sending ERC20 prize rewards to the winner in theclaimPrizesfunction. -
L-02 Low Lacking OFFCHAIN Prize Logic Warning Resolved
Description
In the
Seasoncontract there is no logic in theclaimPrizesfunction for thePrizeType.OFFCHAINprize type. This may be the expected behavior and all logic might be computed off-chain, however it may be useful to emit an event in theclaimPrizesfunction to indicate that off-chain rewards should be delivered, for instance it may be useful to see which off-chain itemuuidshould be received by the user.Recommendation
Consider whether the
claimPrizesfunction should include any logic, such as an event to indicate that off chain prizes have been claimed. -
L-03 Low Malicious Token Name Risk Validation Acknowledged
Description
The
LiquidityLaunchFactorycontract allows users to deploy a custom token including the ability to set anameandsymbolfor the new token. However, it is possible for an attacker to craft anamesuch that it includes markup that can contain Javascript code. If loaded into a frontend without XSS protection, this can cause potential harm to users of the platform as was the case in the EtherDelta exploit. For more details on the EtherDelta exploit please refer to this article: https://medium.com/@zhongqiangc/code-injection-attacks-using-blockchains-part-1-db4e121890bbRecommendation
Consider adding a length limit to the token name in addition to the pre-existing validation for the symbol.
-
L-04 Low Prizes Claimed Before All Winners Selected Unexpected Behavior Resolved
Description
In the
claimPrizesfunction there is no requirement that all winners have been chosen when the claim is taking place. This may be unexpected for the Zap team.Recommendation
Consider whether all winners should be chosen before any claims are allowed to occur, and if so then implement the corresponding validation in the
claimPrizesfunction. -
L-05 Low Winners Should Always Be Able To Receive NFTs Warning Acknowledged
Description
In the
claimPrizesfunction when awarding a winner with an ERC721 token they have wonsafeTransferFromis used which will revert if the receiver is a smart contract and does not implement theonErc721Receivedfunction. For participants that use a smart contract address this will prevent them from claiming all of their rewards.Recommendation
Be sure to document this risk to users so this issue does not arise.
-
L-06 Low Typo Typo Resolved
Description
In the comment for the
onERC721Receivedfunction the word receiver is misspelled asreciever.Recommendation
Correct the spelling to
receiver. -
L-07 Low uploadEntrants Called After Winner Selection Validation Resolved
Description
In the Season contract the
uploadEntrantsfunction can be called after winners have already been chosen. This action would not normally be taken by an admin, however it should be explicitly prevented to avoid any wasted gas if the transaction were to be accidentally sent.Recommendation
Add validation to the
uploadEntrantsfunction that asserts that no winners have been chosen. -
L-08 Low isValidRequestHash Hardcoded To True Warning Acknowledged
Description
The
isValidRequestHashvariable is currently hardcoded to true with the comment thatThis is a temporary fix to make the test pass.Recommendation
Be sure to resolve this comment and use the actual logic for the
isValidRequestHashvalue. -
L-09 Low Unnecessary If Case Optimization Acknowledged
Description
In the
fulfillRandomnessfunction there is an if case which relies on theisValidRequestHashvalue after theisValidRequestHashvalue has already been required to be true.Recommendation
Either remove the require statement or the if case as both are unnecessary together.
-
L-10 Low Some Seasons May Be Hard To Complete Warning Resolved
Description
In the event that one or just a couple users have purchased the majority of tickets in a season it may be a very high probability that a previous winner is selected in the
_fulfillRandomnessFunction. If the odds are sufficiently large for a small group of winners then it may be unrealistic to be able to complete a Season. Additionally this will cost an unnecessary amount of gas fees to the operator and additional fees to cover the Gelato cost.Recommendation
Be aware of this risk and ensure Seasons have a wide distribution of users with no single large ticket holders. Furthermore if this behavior should be avoided, consider adding an escape hatch to end a Season early or even removing winners from the
entrantAddressesandcumulativeTickets, though this will likely introduce more complexity than the resolution is worth. -
L-11 Low Requested Hash Uncleared Best Practices Acknowledged
Description
In the
_requestRandomnessfunction therequestedHashentry for the request is not cleared. This diverts from the latestGelatoVRFConsumerBasecontract implementation here: https://github.com/gelatodigital/vrf-contracts/blob/main/contracts/GelatoVRFConsumerBase.solRecommendation
Consider adding logic to delete the
requestedHashin the event that the random number was accepted. -
L-12 Low Modulo Bias Warning Acknowledged
Description
The
randomTicketNumbervalue is computed using modulo for the final randomized value, however this can lead to slightly unfair probability distributions due to what is known as modulo bias. For example, consider the simplified scenario where thetotalTicketsis 3 and the random number is generated from 0 to 10 inclusive. There are three outcomes for therandomTicketNumber, however the probabilities which are shown below are not equal. <table> <tr> <td>Random</td> <td>Result Mod 3</td> </tr> <tr> <td>0</td> <td>0</td> </tr> <tr> <td>1</td> <td>1</td> </tr> <tr> <td>2</td> <td>2</td> </tr> <tr> <td>3</td> <td>0</td> </tr> <tr> <td>4</td> <td>1</td> </tr> <tr> <td>5</td> <td>2</td> </tr> <tr> <td>6</td> <td>0</td> </tr> <tr> <td>7</td> <td>1</td> </tr> <tr> <td>8</td> <td>2</td> </tr> <tr> <td>9</td> <td>0</td> </tr> <tr> <td>10</td> <td>1</td> </tr> </table>Probabilities for
randomTicketNumber: <table> <tr> <td>randomTicketNumber</td> <td>Probability</td> </tr> <tr> <td>0</td> <td>4/11 = 36.36%</td> </tr> <tr> <td>1</td> <td>4/11 = 36.36%</td> </tr> <tr> <td>2</td> <td>3/11 = 27.27%</td> </tr> </table> In actuality thetotalTicketswill be much larger and the random number is generated in the entire range of auint256word, however this simplified example demonstrates how the probabilities may not be even amongst all ticket numbers when the modulo does not dividetype(uint256).max + 1.Recommendation
In actuality the modulo bias experienced by Zap will be reduced notably as higher ticket amounts are minted, so it is likely that this bias can be safely ignored for the purposes of Zap’s Season contract. However if modulo bias should be a priority to remove, one approach could be to require that tickets purchased always result in a modulo divisor that divides the cardinality of possible random numbers, e.g. type(uint256).max + 1 (all positive integers in a uint256 and 0).
-
L-13 Low Lacking Nonzero Tickets Validation Validation Resolved
Description
**In the **
uploadEntrantsfunction there is no validation that the provideduser.ticketsvalue is greater than 0. If auser.ticketsvalue of 0 were to be used then a user can exist in theentrantAddressesarray multiple times which would be unexpected behavior.Recommendation
The
uploadEntrantsfunction is performed by a trusted party, however to avoid any unintentional mistakes it may be prudent to add validation which prevents auser.ticketsvalue of 0. -
L-14 Low Typo Typo Resolved
Description
In the
LiquidityFreeLaunchcontract thezapDegenTokenRecievervariable misspells receiver asreciever.Recommendation
Correct the spelling to
receiver. -
L-15 Low Treasury Fee Taken Before Seed Amount Unexpected Behavior Acknowledged
Description
In the
seedWithFeeAndAcceptTokenfunction thefeeForZapTreasuryis bought with thegetTokensOutForEthDepositand the results are added to thetotalSoldandbalancevalues before the initial seed amount is added. This results in the fee being larger and the seededamountOutbeing smaller. Using explicative numbers, if thefeeForZapTreasuryis 0.01 ETH and theseedAmountis 0.1 ETH then:For the
feeForZapTreasury:A = (1 + k * 0.01)^n = 1.00593startSupply = 0endSupply = totalTokens * (1 - 1/A) = 910_374_363 * (1 - 1/1.00593) = 5_366_695amountOutForZapTreasury = 5_366_695 tokensFor the
amountOut:A = (1 + k * (0.1 + 0.01))^n = 1.06696startSupply = 5_366_695 tokensendSupply = totalTokens * (1 - 1/A) = 910_374_363 * (1 - 1/1.06696) = 57_133_039amountOut = 57_133_039 - 5_366_695 = 51_766_344 tokensHowever if the ordering were reversed the
amountOutis larger and the zap treasury fee is smaller.For the
amountOut:A = (1 + k * (0.1))^n = 1.06072startSupply = 0 tokensendSupply = totalTokens * (1 - 1/A) = 910_374_363 * (1 - 1/1.06072) = 52_113_593amountOut = 52_113_593 tokensFor the
feeForZapTreasury:A = (1 + k * (0.1 + 0.01))^n = 1.06696startSupply = 52_113_593 tokensendSupply = totalTokens * (1 - 1/A) = 910_374_363 * (1 - 1/1.06696) = 57_133_039amountOutForZapTreasury = 57_133_039-52_113_593 = 5_019_446 tokensRecommendation
This may be the expected behavior since the fee can be reduced for larger initial seed amounts, however it should be confirmed whether this ordering is expected and clearly documented for users.
-
L-16 Low Unused Constant Optimization Resolved
Description
In the
LiquidityFreeLaunchcontract theDUST_THRESHOLDconstant is unused.Recommendation
Remove or implement the use case for the
DUST_THRESHOLD.
Remediation Review
1 finding · November 29, 2024-
L-01 Low _fulfillRandomness Gas Expenditure Warning Reported
Description
In the
_fulfillRandomnessfunction awhile(true)loops is now used to re-draw random numbers to avoid selecting a winner that has already been chosen. In cases where a single user has the majority of tickets in a raffle this loop may execute many times before finding a unique winner, and may even need more gas than the block gas limit. This will incur a large gas fee and thus a large gelato fee to Zap and may not result in a unique winner being chosen. To limit the gas expenditure for cases where the loop will execute many times a while loop iteration limit can be introduced.Recommendation
Consider introducing an iteration limit in the
_fulfillRandomnessfunction to ensure that unexpectedly large gas fees are not levied on the protocol.
No findings match.
More from Zap
Put your code through the same review.
This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.
