After a line by line manual analysis and automated review, Guardian Audits has concluded that:
- Published
- Language
- Solidity
- Chains
- Fantom
- Sector
- Gaming and prediction
- 3 Critical
- 11 High
- 3 Medium
- 21 Low
- 0 Informational
Scope
Overview
After a line by line manual analysis and automated review, Guardian Audits has concluded that:
- UltiBets’s smart contracts have an HIGH RISK SEVERITY
- UltiBet’s smart contracts have an ACTIVE OWNERSHIP
- UltiBets’s smart contract owner has multiple “write” privileges. Centralization risk correlated to the active ownership is VERY HIGH
Findings 38
-
GLOBAL-1 Medium Poor Practices Best Practices Resolved
Description
Throughout the contracts there are myriad instances of:
- Lack of camelcase
- Unnecessary local variables which waste gas
- Redundant boolean checks e.g.
== true - Typos in the comments
- Unnecessary for-loops which waste gas and enable DoS
- Inefficient computations e.g.
feeBalance -= feeBalance - Many functions can be declared
external
Recommendation
Use camelcase throughout the contracts, remove redundant and unnecessary local variables, do not perform redundant boolean checks, revise comments, refactor operations to avoid unnecessary for-loops, avoid inefficient computations e.g. use
feeBalance = 0, declare all functions that are not called within the contractsexternalrather thanpublic -
GLOBAL-2 High Centralization Risk Centralization / Privilege Resolved
Description
Throughout the contracts there is a risk of admins and oracles using their privilege to benefit themselves or act malicious toward user’s holdings. Contracts utilizing the CustomAdmin access control model face more risk as the number of admins or oracles increase because it only takes one to act mischievous.
Additionally, the treasury which is under the in-house MultiSig.sol contract can be can be manipulated by the principal admin who can unethically force a quorum.
Recommendation
Ensure that privileged addresses such as admins are all a multi-sig and/or introduce a timelock for improved community oversight. Secure a KYC for increased community trust.
-
UB-1 High DoS Attack Denial-of-Service Resolved
Description
A malicious actor can call
placeBetwith different addresses, sending a tiny amount of ETH per call.As a result, the YesBettors array and NoBettors array will expand to the point where it either exceeds the block gas limit, or costs too much to
reportResult. This will render the function inoperable.Recommendation
Use a pull-over-push withdrawal pattern such that the “for” loop can be avoided.
-
UB-2 High DoS Attack Denial-of-Service Resolved
Description
feeBettorBetis calculated based on total amount bet for a user i.e. winning side + losing side.If the user bet more on the losing side, it is possible for the
feeBettorBetto exceed the amount bet on the winning side, causing a subtraction underflow which will revert. Therefore, reportResult will consistently fail and users will not get their winnings.Recommendation
Calculate the fee based on the user’s bet for the winning side. Or, place a cap on the fee.
-
UB-3 High Lost Funds On Cancel Logical Error Resolved
Description
The contract allows placing bets on both sides which makes sense if the user would like to perform arbitrage. If the event is canceled upon an emergency, a user can only withdraw funds from one side. If they were betting on both sides for arbitrage, they lose the amount they bet on the other side.
Recommendation
Don’t set both sides of a user’s bet to 0.
-
UB-4 Low Requires in For Loop Best Practices Resolved
Description
Place the
requirestatements before the if statement inreportResult. It is not necessary to check those conditions upon each loop and also redundant to have them in loops for both sides while being extremely gas expensive.Recommendation
Move the
requirestatements to the top of the function. -
UB-5 Low Inaccurate Enum Comment Inaccurate Comments Resolved
Description
The comment states that 1 represent
YESand 0 representsNO. BecauseYESis the the first and default value of the enum,YESis 0 and 1 isNO.Recommendation
Update the comment to accurately reflect the enum.
-
UB-6 Low Declare Variables Immutable Best Practices Resolved
Description
feePercentageandUltibetsTreasuryare never mutated once set.Recommendation
Add the “immutable” keyword to
UltiBetsTreasuryand add the “constant” keyword tofeePercentagesince it is not being set in the constructor.. -
UBT-1 Critical Stuck ETH Funds Logical Error Resolved
Description
There is no way to withdraw the Ether sent to the treasury. Contracts like
SquidBetPrizePoolsend ether to the treasury whenEmergencySafeWithdrawis called.Recommendation
Add a function to convert the Ether to the fundingToken, or implement allocations to be able to use the Ether.
-
UBT-2 Critical Lack of Access Control Logical Error Resolved
Description
Functions
deleteAllocationandchangeSalaryhave no Admin requires so anyone can delete an allocation and change a team member’s salary.Recommendation
Add a check that the
msg.sender is an admin. -
UBT-3 Medium Payment Pushed Back Centralization / Privilege Resolved
Description
Admin can just keep calling
createAllocationso the allocation for a particular address cannot be withdrawn aspayoutdaykeeps getting pushed back.Recommendation
Adopt a solution that doesn’t allow such manipulation, or ensure trust via a multi-sig for every privileged address.
-
UBT-4 Critical Arbitrary Salary Logical Error Resolved
Description
The
salarycould be set arbitrarily high before someone callswithdrawto drain thefundingTokenbalance. Thesalarycould also be set arbitrarily high to prevent withdrawal duringtotalpayoutcalculation by causing an overflow.Recommendation
Add a cap to the salary and restrict access to
changeSalary. -
UBT-5 Low Set Withdrawal Frequency Best Practices Resolved
Description
withdrawalFrequencycan be set in the constructor. It doesn’t need to be updated each time an allocation is created. Especially sincewithdrawalFrequencyis used in other functions likewithdrawRecommendation
Set the withdrawal frequency in the constructor.
-
UBT-6 Low Declare Variable Immutable Best Practices Resolved
Description
Adminis not mutated outside of the constructor so it can be declaredimmutable.Recommendation
Declare Admin with the
immutablekeyword. -
MS-1 High Principal Admin Abuse Centralization / Privilege Resolved
Description
The
principalAdmincan keep callingsetApproverAddrto add as many addresses as needed to reachquorumand approve any arbitrary transaction. This is not a true multi-sig if a principal admin can obtain all the power and decision making.Recommendation
Make it so a majority of admins must agree to add or remove another admin or partake in other important decisions with the treasury.
-
MS-2 Low Lack of 0 Check Best Practices Resolved
Description
There is no check to make sure that
quorumis not set to 0.Recommendation
Add a require that
quorumis greater than 0. -
MS-3 Low Break For Loop Optimization Resolved
Description
If you found that the
msg.senderis an admin you can break out of the for loop to save gas.Recommendation
Break out of the for loop once the
msg.senderis verified to be an admin -
20A-1 Low Unnecessary transferFrom Best Practices Resolved
Description
Because the transfer is from the current address to another address, the
ERC20functiontransfercould be used.Recommendation
Replace the use of
transferFromwithtransfer. Be sure to check the return value or opt for asafeTransferalternative. -
721A-1 High Wrong Token Sent Logical Error Resolved
Description
The
tokenId+1 is sent to the caller although it was not used in the check for a valid leaf. Instead,tokenIdwas verified to correspond with themsg.sender.Recommendation
Send the current
tokenIdtomsg.sender. -
721A-2 Low Inaccurate Comment Inaccurate Comments Resolved
Description
The comment states that the proof is to check that the address and the
amountare in tree. However, for the ERC721 airdrop, you are checking if the address andtokenIdare in the tree.Recommendation
Update the comment to reflect the
ERC721NFT airdrop. -
CA-1 Low Inaccurate Comment Inaccurate Comments Resolved
Description
The comment states that the function adds the specified address to the list of
administratorsbut it adds the address to the mapping ofOracles.Recommendation
Update the comment to reflect what the function does.
-
SQD[1-F]-1 High Cannot Stop Betting Logical Error Resolved
Description
In the
placeBetfunction there is no check to ensure thatisEventCancelledis false.Recommendation
Add a
requirestatement to all of theseplaceBetfunctions such that you cannot place a bet if the event is cancelled. -
SQD[1-F]-2 Low Invalid Bet Side Management Logical Error Resolved
Description
In the
placeBetfunction the following assignmentplayerSide[msg.sender] += choiceshould simply readplayerSide[msg.sender] = choiceRecommendation
Refactor this line to be
playerSide[msg.sender] = choice. -
SQD[1-F]-3 High Winner Can Be Changed Logical Error Resolved
Description
Once the event is finished, it is possible to for a malicious
oracleto callreportResultseveral times to add both sides as the winner.Recommendation
Prevent the
resultfrom being changed by using a variable to check if theresultwas already reported. -
SQD[1-4]-4 Medium Unnecessary For Loop Optimization Resolved
Description
In
reportResulttheforloop that loops through each bettor in theplayersSidemapping is gas expensive and unnecessary.Recommendation
Infer whether or not players are winners based on the
resultcontract variable, don’t maintain theiswinnermapping. -
SQDF-1 High Winner Can Be Changed Logical Error Resolved
Description
Once the event is finished, it is possible to for a malicious
oracleto repeatedly callreportResultto modify which side is the winner.Recommendation
Prevent the
resultfrom being changed by using a variable to check if theresultwas already reported. -
SQDF-2 High Weak Source of Randomness Randomness Resolved
Description
pickWinneruses weak sources of on-chain randomness. A validator can exploit this in order to obtain a winner that is beneficial to themselves.In addition, an Admin can keep calling
pickWinner, then reportResult to clearisCompetitionEnded, then callpickWinneragain and so on until the winner is favorable to them.Recommendation
Utilize a strong source of randomness whether it be the on-chain randomness pattern or an oracle. In addition, prevent repeated calls to
pickWinnerby tracking whether a winner was already chosen. -
SQDF-3 Low Unnecessary Casting Best Practices Resolved
Description
There is no need to cast a positive integer to a
uint.Recommendation
Remove the unnecessary
uintsurrounding 1 and 2. -
SQDF-4 Low Unnecessary For Loop Optimization Resolved
Description
The
forloop over an arbitrary number of votes can be avoided by tallying the votes in theVotefunction.Recommendation
Get rid of the
forloop and move on-demand tallying logic toVote. -
SQDF-5 Low Unnecessary Variable Optimization Resolved
Description
The
playerVotestate variable is never meaningfully used.Recommendation
Remove the
playerVotevariable. -
SQDR-1 Low Unnecessary Increment Optimization Resolved
Description
It is a waste of gas to initialize the
nextPlayerNumberto 0 and then immediately increment it in the constructor.Recommendation
Simply initialize
nextPlayerNumberto 1 rather than initializing it to 0 and spending gas to increment it in the constructor. -
SQDR-2 Low Inaccurate Error Message Inaccurate Message Resolved
Description
The error message indicates that the cost is 0.01 Ether while it is in fact 1 Ether.
Recommendation
Update the message to reflect the real cost.
-
SQDR-3 Low Invalid Player Number Management Logical Error Resolved
Description
The
playersNumbersmapping is updated as follows:playersNumbers[msg.sender] +=nextPlayerNumber. It should instead be assigned like soplayersNumbers[msg.sender] =nextPlayerNumberRecommendation
Update the assignment to use
=rather than+=. -
SQDR-4 Low Function Typo Typo Resolved
Description
The function
getIsRegisterdPlayercontains a typoRecommendation
Update the name to be
getIsRegisteredPlayer. -
SQDR-5 Low Redundant Variables Optimization Resolved
Description
The
numberOfPlayersRegisteredvariable can simply be derived as one less than thenextplayerNumberand is therefore unnecessary.Recommendation
Remove the
numberOfPlayersRegisteredvariable and rely on thenextplayerNumber - 1. -
SQPR-1 Low Lack of 0 Checks Best Practices Resolved
Description
Nothing prevents the
UltiBetsTreasuryfrom being set to the zero address in the constructor.In
addWinnerAddress, the winner address can be set to 0.Recommendation
Add zero address checks using require statements.
-
SQPR-2 High Winners Can’t Get Prize Denial-of-Service Resolved
Description
If there are a large amount of winners, the amount of gas can exceed the block limit and winners will not be able to get the prize money,
Recommendation
Utilize a pull-over-push withdrawal patten.
-
SQPR-3 Low Unnecessary Function Optimization Resolved
Description
There is not a need to have both
winnerClaimPrizePoolandwinnersClaimEqualPrizePool. A single winner address could be stored as an “equal” winner and the prize money would just be sent to that single address.Recommendation
Remove the need to store the
winnerAddressand solely store winners inequalWinners.
No findings match.
More from UltiBets
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.
