Skip to content
$1,000,000 in security audit grants are live now, Apply here →

Security review · June 2022

Protocol Review, Part 1

for UltiBets

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

38 resolved

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

  1. GLOBAL-1 Medium Poor Practices Best Practices Resolved
    Location
    Global

    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 contracts external rather than public

  2. GLOBAL-2 High Centralization Risk Centralization / Privilege Resolved
    Location
    Global

    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.

  3. UB-1 High DoS Attack Denial-of-Service Resolved
    Location
    UltiBets.sol:174

    Description

    A malicious actor can call placeBet with 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.

  4. UB-2 High DoS Attack Denial-of-Service Resolved
    Location
    UltiBets.sol:194

    Description

    feeBettorBet is 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 feeBettorBet to 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.

  5. UB-3 High Lost Funds On Cancel Logical Error Resolved
    Location
    UltiBets.sol:160

    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.

  6. UB-4 Low Requires in For Loop Best Practices Resolved
    Location
    UltiBets.sol:195-198, 215-218,

    Description

    Place the require statements before the if statement in reportResult. 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 require statements to the top of the function.

  7. UB-5 Low Inaccurate Enum Comment Inaccurate Comments Resolved
    Location
    UltiBets.sol:105

    Description

    The comment states that 1 represent YES and 0 represents NO. Because YES is the the first and default value of the enum, YES is 0 and 1 is NO.

    Recommendation

    Update the comment to accurately reflect the enum.

  8. UB-6 Low Declare Variables Immutable Best Practices Resolved
    Location
    UltiBets.sol:41

    Description

    feePercentage and UltibetsTreasury are never mutated once set.

    Recommendation

    Add the “immutable” keyword to UltiBetsTreasury and add the “constant” keyword to feePercentage since it is not being set in the constructor..

  9. UBT-1 Critical Stuck ETH Funds Logical Error Resolved
    Location
    UltiBetsTreasury.sol

    Description

    There is no way to withdraw the Ether sent to the treasury. Contracts like SquidBetPrizePool send ether to the treasury when EmergencySafeWithdraw is called.

    Recommendation

    Add a function to convert the Ether to the fundingToken, or implement allocations to be able to use the Ether.

  10. UBT-2 Critical Lack of Access Control Logical Error Resolved
    Location
    UltiBetsTreasury.sol

    Description

    Functions deleteAllocation and changeSalary have 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.

  11. UBT-3 Medium Payment Pushed Back Centralization / Privilege Resolved
    Location
    UltiBetsTreasury.sol

    Description

    Admin can just keep calling createAllocation so the allocation for a particular address cannot be withdrawn as payoutday keeps getting pushed back.

    Recommendation

    Adopt a solution that doesn’t allow such manipulation, or ensure trust via a multi-sig for every privileged address.

  12. UBT-4 Critical Arbitrary Salary Logical Error Resolved
    Location
    UltiBetsTreasury.sol:184

    Description

    The salary could be set arbitrarily high before someone calls withdraw to drain the fundingToken balance. The salary could also be set arbitrarily high to prevent withdrawal during totalpayout calculation by causing an overflow.

    Recommendation

    Add a cap to the salary and restrict access to changeSalary.

  13. UBT-5 Low Set Withdrawal Frequency Best Practices Resolved
    Location
    UltiBetsTreasury.sol:

    Description

    withdrawalFrequency can be set in the constructor. It doesn’t need to be updated each time an allocation is created. Especially since withdrawalFrequency is used in other functions like withdraw

    Recommendation

    Set the withdrawal frequency in the constructor.

  14. UBT-6 Low Declare Variable Immutable Best Practices Resolved
    Location
    UltiBetsTreasury.sol:13

    Description

    Admin is not mutated outside of the constructor so it can be declared immutable.

    Recommendation

    Declare Admin with the immutable keyword.

  15. MS-1 High Principal Admin Abuse Centralization / Privilege Resolved
    Location
    MultiSig.sol:51

    Description

    The principalAdmin can keep calling setApproverAddr to add as many addresses as needed to reach quorum and 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.

  16. MS-2 Low Lack of 0 Check Best Practices Resolved
    Location
    MultiSig.sol:19

    Description

    There is no check to make sure that quorum is not set to 0.

    Recommendation

    Add a require that quorum is greater than 0.

  17. MS-3 Low Break For Loop Optimization Resolved
    Location
    MultiSig.sol:32

    Description

    If you found that the msg.sender is an admin you can break out of the for loop to save gas.

    Recommendation

    Break out of the for loop once the msg.sender is verified to be an admin

  18. 20A-1 Low Unnecessary transferFrom Best Practices Resolved
    Location
    ERC20Airdrop.sol:65

    Description

    Because the transfer is from the current address to another address, the ERC20 function transfer could be used.

    Recommendation

    Replace the use of transferFrom with transfer. Be sure to check the return value or opt for a safeTransfer alternative.

  19. 721A-1 High Wrong Token Sent Logical Error Resolved
    Location
    ERC721Airdrop.sol:56

    Description

    The tokenId+1 is sent to the caller although it was not used in the check for a valid leaf. Instead, tokenId was verified to correspond with the msg.sender.

    Recommendation

    Send the current tokenId to msg.sender.

  20. 721A-2 Low Inaccurate Comment Inaccurate Comments Resolved
    Location
    ERC721Airdrop.sol:42

    Description

    The comment states that the proof is to check that the address and the amount are in tree. However, for the ERC721 airdrop, you are checking if the address and tokenId are in the tree.

    Recommendation

    Update the comment to reflect the ERC721 NFT airdrop.

  21. CA-1 Low Inaccurate Comment Inaccurate Comments Resolved
    Location
    CustomAdmin.sol:46

    Description

    The comment states that the function adds the specified address to the list of administrators but it adds the address to the mapping of Oracles.

    Recommendation

    Update the comment to reflect what the function does.

  22. SQD[1-F]-1 High Cannot Stop Betting Logical Error Resolved
    Location
    SquidBet*Round.sol

    Description

    In the placeBet function there is no check to ensure that isEventCancelled is false.

    Recommendation

    Add a require statement to all of these placeBet functions such that you cannot place a bet if the event is cancelled.

  23. SQD[1-F]-2 Low Invalid Bet Side Management Logical Error Resolved
    Location
    SquidBet*Round.sol

    Description

    In the placeBet function the following assignment playerSide[msg.sender] += choice should simply read playerSide[msg.sender] = choice

    Recommendation

    Refactor this line to be playerSide[msg.sender] = choice.

  24. SQD[1-F]-3 High Winner Can Be Changed Logical Error Resolved
    Location
    SquidBet*Round.sol

    Description

    Once the event is finished, it is possible to for a malicious oracle to call reportResult several times to add both sides as the winner.

    Recommendation

    Prevent the result from being changed by using a variable to check if the result was already reported.

  25. SQD[1-4]-4 Medium Unnecessary For Loop Optimization Resolved
    Location
    SquidBet*Round.sol

    Description

    In reportResult the for loop that loops through each bettor in the playersSide mapping is gas expensive and unnecessary.

    Recommendation

    Infer whether or not players are winners based on the result contract variable, don’t maintain the iswinner mapping.

  26. SQDF-1 High Winner Can Be Changed Logical Error Resolved
    Location
    SquidBetFinalRound.sol: 111

    Description

    Once the event is finished, it is possible to for a malicious oracle to repeatedly call reportResult to modify which side is the winner.

    Recommendation

    Prevent the result from being changed by using a variable to check if the result was already reported.

  27. SQDF-2 High Weak Source of Randomness Randomness Resolved
    Location
    SquidBetFinalRound.sol: 193

    Description

    pickWinner uses 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 clear isCompetitionEnded, then call pickWinner again 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 pickWinner by tracking whether a winner was already chosen.

  28. SQDF-3 Low Unnecessary Casting Best Practices Resolved
    Location
    SquidBetFinalRound.sol:162-164

    Description

    There is no need to cast a positive integer to a uint.

    Recommendation

    Remove the unnecessary uint surrounding 1 and 2.

  29. SQDF-4 Low Unnecessary For Loop Optimization Resolved
    Location
    SquidBetFinalRound.sol:162-166

    Description

    The for loop over an arbitrary number of votes can be avoided by tallying the votes in the Vote function.

    Recommendation

    Get rid of the for loop and move on-demand tallying logic to Vote.

  30. SQDF-5 Low Unnecessary Variable Optimization Resolved
    Location
    SquidBetFinalRound.sol

    Description

    The playerVote state variable is never meaningfully used.

    Recommendation

    Remove the playerVote variable.

  31. SQDR-1 Low Unnecessary Increment Optimization Resolved
    Location
    SquidBetPlayersRegistration.sol: 30

    Description

    It is a waste of gas to initialize the nextPlayerNumber to 0 and then immediately increment it in the constructor.

    Recommendation

    Simply initialize nextPlayerNumber to 1 rather than initializing it to 0 and spending gas to increment it in the constructor.

  32. SQDR-2 Low Inaccurate Error Message Inaccurate Message Resolved
    Location
    SquidBetPlayersRegistration.sol: 45

    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.

  33. SQDR-3 Low Invalid Player Number Management Logical Error Resolved
    Location
    SquidBetPlayersRegistration.sol: 55

    Description

    The playersNumbers mapping is updated as follows: playersNumbers[msg.sender] += nextPlayerNumber. It should instead be assigned like so playersNumbers[msg.sender] = nextPlayerNumber

    Recommendation

    Update the assignment to use = rather than +=.

  34. SQDR-4 Low Function Typo Typo Resolved
    Location
    SquidBetPlayersRegistration.sol: 63

    Description

    The function getIsRegisterdPlayer contains a typo

    Recommendation

    Update the name to be getIsRegisteredPlayer.

  35. SQDR-5 Low Redundant Variables Optimization Resolved
    Location
    SquidBetPlayersRegistration.sol

    Description

    The numberOfPlayersRegistered variable can simply be derived as one less than the nextplayerNumber and is therefore unnecessary.

    Recommendation

    Remove the numberOfPlayersRegistered variable and rely on the nextplayerNumber - 1.

  36. SQPR-1 Low Lack of 0 Checks Best Practices Resolved
    Location
    SquidBetPrizePool.sol

    Description

    Nothing prevents the UltiBetsTreasury from 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.

  37. SQPR-2 High Winners Can’t Get Prize Denial-of-Service Resolved
    Location
    SquidBetPrizePool.sol:61-68

    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.

  38. SQPR-3 Low Unnecessary Function Optimization Resolved
    Location
    SquidBetPrizePool.sol

    Description

    There is not a need to have both winnerClaimPrizePool and winnersClaimEqualPrizePool. 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 winnerAddress and solely store winners in equalWinners.

More from UltiBets

  1. Protocol Review, Part 2

    28 findings2 critical · 3 high 28 findings: 2 critical, 3 high, 8 medium, 15 low

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.

Get a quote