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

Security review · July 2022

Protocol Review, Part 2

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
  • 2 Critical
  • 3 High
  • 8 Medium
  • 15 Low
  • 0 Informational

25 resolved · 3 acknowledged

Scope

Overview

After a line by line manual analysis and automated review, Guardian Audits has concluded that:

  • UltiBet’s smart contracts have an LOW RISK SEVERITY
  • UltiBet’s smart contracts have an ACTIVE OWNERSHIP
  • UltiBet’s smart contract owner has multiple “write” privileges. Centralization risk correlated to the active ownership is MEDIUM

Findings 28

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

    Description

    Throughout the contracts there are myriad instances of:

    • Lack of camelcase
    • Use of SafeMath when Solidity version ^0.8.0 has overflow and underflow checks
    • Unnecessary local variables which waste gas e.g. address to = payable(ultibetsTreasury)
    • Redundant boolean checks e.g. == true
    • Typos in the comments
    • 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, remove SafeMath operations to save gas, declare functions not used internally as external.

    Resolution

    • Typos, code-style, function declarations, and frivolous code has been addressed.
  2. GLOBAL-2 High Centralization Risk Centralization / Privilege Acknowledged
    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.

    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.

    Resolution

    • The risk is acknowledged and will be limited with appropriate measures such as multi-sig addresses and community transparency.
  3. UB-1 Critical Total Bets Not Updated Logical Error Resolved
    Location
    UltiBets.sol

    Description

    The bets mapping is not being updated in placeBet. When withdrawGain is called, .div(bets[result.winner]) would lead to a division by 0 error every time. Therefore, no one would be able to withdraw their gains, leading to complete loss of funds.

    Recommendation

    In placeBet, increment the bets mapping with bets[_side] += betAmount.

    Resolution

    • The bets mapping is now updated in placeBet.
  4. UB-2 High Order of Operations Logical Error Resolved
    Location
    UltiBets.sol

    Description

    The logic does not correctly calculate the gain due to the SafeMath order of operations. With the SafeMath operations the addition is performed first, but what is needed is to first calculate the winner’s part of the loser funds and then finally adding it to BettorBetWinner.

    For example:

    3.add(10).mul(3).div(6) = 6

    3 + 10 * 3 / 6 = 8

    Recommendation

    Replace with BettorBetWinner + bets[result.loser] * BettorBetWinner / bets[result.winner].

    Resolution

    • The logic has been replaced as suggested.
  5. UB-3 Medium Report Result Twice Logical Error Resolved
    Location
    UltiBets.sol: 174

    Description

    reportResult does not require the event to be finished. Therefore, the result can be reported multiple times and the winner and loser swapped while the treasury fee is taken multiple times.

    Recommendation

    Add require(!isEventFinished) in reportResult so a result cannot be reported twice.

    Resolution

    • The suggested requires has been added.
  6. UB-4 Medium Same Winner and Loser Logical Error Resolved
    Location
    UltiBets.sol: 174

    Description

    reportResult does not prevent the oracle from setting the winner and loser to the same side which can lead to loss of funds for many users.

    Consider a 100 ether bet on one side and 10 ether bet on another, but the 100 ether side is chosen as both the winner and loser. From the calculation in withdrawGain, the contract will attempt to payout a total of 200 ether while it only holds 110. Therefore the users who claim first will deplete the contract and the ones who claim later will experience a complete loss of funds.

    Recommendation

    Add a safety check that the arguments _winner and _loser are different.

    Resolution

    • The suggested requires has been added.
  7. UB-5 Low Potential DoS Denial-of-Service Resolved
    Location
    UltiBets.sol: 185-187

    Description

    reportResult depends on the ETH transfer to the treasury to be successful in order for the result to be reported. Such a call can fail, leading to a DoS condition.

    Recommendation

    Remove lines 185-187 as there is no need to have the transfer logic there. External calls should ideally be isolated into another transaction and the admin calling withdrawEarnedFees serves that purpose.

    Resolution

    • Unnecessary transfer logic has been removed.
  8. UB-6 Low Inaccurate Comment Inaccurate Comments Resolved
    Location
    UltiBets.sol: 144

    Description

    The comment for stopBet states that “... this action can only be performed by an administrator” but the modifier is OnlyOracle. In addition, the comment states that it is an emergency function, yet it is required to be called in order for a bettor to withdrawGain.

    Recommendation

    Update the comments to accurately reflect the function. In addition, verify if stopBet is needed. If it is truly only used for emergency situations, it does not make sense to have it required so user’s can withdraw their gains.

    Resolution

    • The comment has been updated.
  9. UB-7 Low Superfluous Code Optimization Resolved
    Location
    UltiBets.sol: 21, 22

    Description

    The betAmountForYes, and betAmountForNo variables are never assigned in the bettorBetHistory mapping.

    Recommendation

    Remove them from the struct or use them to replace the betsAmountPerBettor mapping.

    Resolution

    • The variables have been removed.
  10. UB-8 Low Superfluous Code Optimization Resolved
    Location
    UltiBets.sol: 36, 37

    Description

    The inArrayYes and inArrayNo variables are never used.

    Recommendation

    Remove them.

    Resolution

    • The variables have been removed.
  11. UBT-1 Critical Treasury Cannot Receive ETH Logical Error Resolved
    Location
    UltiBetsTreasury.sol

    Description

    Because there is no receive or fallback function, any contract that relies on sending ETH to the treasury will face unintended consequences such as stuck fees.

    Recommendation

    Add a receive() external payable { } function to the contract.

    Resolution

    • A receive function has been added.
  12. UBT-2 Low Superfluous Code Optimization Resolved
    Location
    UltiBetsTreasury.sol

    Description

    The require(msg.sender == Admin) can be deduplicated into an onlyAdmin modifier used for every permissioned function.

    Recommendation

    Deduplicate the require logic into an onlyAdmin modifier and apply the onlyAdmin modifier to every permissioned function.

    Resolution

    • The modifier has been created and added to relevant functions.
  13. UBT-3 Low Typo Typo Resolved
    Location
    UltiBetsTreasury.sol: 148

    Description

    The _descreption parameter in createAllocation is misspelled.

    Recommendation

    Correct it to _description.

    Resolution

    • The typo has been fixed.
  14. UBT-4 Low Superfluous Code Optimization Resolved
    Location
    UltiBetsTreasury.sol: 198

    Description

    Since the amount variable is only used once and the allocations[msg.sender].salary value is not changed before the use of amount, it is unnecessary to declare the amount variable.

    Recommendation

    Remove the declaration and use of the amount variable for gas optimization.

    Resolution

    • The variable has been removed.
  15. UBT-5 Low Vague Event Information Events Resolved
    Location
    UltiBetsTreasury.sol: 243

    Description

    The SalaryChanged event is emitted with just the block.timestamp and _newSalary, leaving no record of which address the salary was changed for.

    Recommendation

    Include the _address as a part of the SalaryChanged event data.

    Resolution

    • The address is now included in the SalaryChanged event data.
  16. UBT-6 Low Inefficient Addition Optimization Resolved
    Location
    UltiBetsTreasury.sol: 274

    Description

    The allocations[msg.sender].totalPayout = allocations[msg.sender].totalPayout.add(amount) computation inefficiently references the allocations state data twice.

    Recommendation

    Use += in order to save gas.

    Resolution

    • += is now used.
  17. MS-1 Low Inaccurate Comment Inaccurate Comments Resolved
    Location
    MultiSig.sol: 98-100

    Description

    The comment states “Public Functions” yet initialize directly below is internal.

    Recommendation

    Move comment to where the section below is solely public functions.

    Resolution

    • The comment has been updated.
  18. UBF-1 Low Inaccurate Variable Name Best Practices Resolved
    Location
    UltiBetFactory.sol: 18

    Description

    The mapping addressToId is really the contract id to the address of the contract.

    Recommendation

    Change name to idToAddress.

    Resolution

    • The name of the mapping has been updated.
  19. 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 updates the address in the mapping of Oracles.

    Recommendation

    Update the comment to reflect what the function does.

    Resolution

    • The comment has been updated.
  20. SQDF-1 Medium Report Result Twice Logical Error Resolved
    Location
    SquidBetFinalRound.sol: 108,119

    Description

    isResultReported is not set to true after the call to reportResult. Therefore, the result can be reported multiple times with varying arguments. Furthermore, line 119 isVotingClosed = false can be used to prevent a winner from ever getting picked in pickWinner().

    Recommendation

    Add isResultReported = true to the end of the function.

    Resolution

    • The suggested addition was made.
  21. SQDF-2 Medium Arbitrary Voting Results Logical Error Resolved
    Location
    SquidBetFinalRound.sol: 163

    Description

    In the resultVote function, the if statements that determines which finalVoteDecision to return are placed inside of the for loop that counts the votes. Therefore the finalVoteDecision will be arbitrarily based on whichever votes happen to be first in the votes list.

    Recommendation

    Move the if statements determining finalVoteDecision after the for loop, or preferably refactor the voting entirely per SQDF-3.

    Resolution

    • The if statement has been moved outside of the for loop
  22. SQDF-3 Low Potential DoS Denial-of-Service Resolved
    Location
    SquidBetFinalRound.sol: 156

    Description

    The for loop is reliant on the number of votes. If the number of votes is very high, the calculation may exceed the block gas limit and fail.

    Recommendation

    Another way to approach this problem is to have an int variable that increases by 1 when the player votes one way and decreases by 1 when the player votes the other way in Vote. The sign of the end value once voting is finished will tell you which choice had more votes.Therefore, a for loop can be entirely avoided.

    Resolution

    • The suggested approach was implemented.
  23. SQDF-4 Low Superfluous Code Optimization Resolved
    Location
    SquidBetFinalRound.sol: 115-116

    Description

    reportResult does not need to contain logic for transferring funds to the prize pool because transferTotalEntryFeestoPrizePool exists.

    Recommendation

    Remove transfer logic from reportResult.

    Resolution

    • Transfer logic was removed from reportResult.
  24. SQDF-5 High Weak Source of Randomness Randomness Acknowledged
    Location
    SquidBetFinalRound.sol: 176, 188

    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.

    Recommendation

    Utilize a strong source of randomness whether it be the on-chain randomness pattern or an oracle.

    Resolution

    • Randomness will be secured with the implementation of Chainlink VRF.
  25. SQDR-1 Medium No Check For Max Players Logical Error Resolved
    Location
    SquidBetPlayersRegistration.sol: 68

    Description

    registerPlayer does not check if registration exceeds the maxNumberOfPlayers. As a result, contracts that loop over the winners such as SquidBetPrizePool may face a DoS attack.

    Recommendation

    Add the check in registerPlayer.

    Resolution

    • The suggested requires has been added.
  26. SQDR-2 Medium Can’t Update Betting Fee Logical Error Resolved
    Location
    SquidBetPlayersRegistration.sol: 112

    Description

    The betting fee is unable to be updated because updateBettingFees updates the maxNumberofPlayers rather than bettingFee.

    Recommendation

    Change the function body to bettingFee = _newBettingFee.

    Resolution

    • The function now updates bettingFee.
  27. SQPR-1 Medium Winners Can’t Get Prize Denial-of-Service Resolved
    Location
    SquidBetPrizePool.sol: 81

    Description

    winnersClaimPrizePool uses a push pattern such that it loops over all winners and sends them their funds. If there are too many equal winners then no one would be able to claim due to the for loop exceeding the block gas limit. Furthermore, if a winner is a contract that is unable to receive ether upon .transfer(), no one would be able to receive their prize money.

    Recommendation

    Utilize a pull-over-push pattern such that when a user calls winnersClaimPrizePool, they receive solely their funds and no other funds are dispatched.

    Resolution

    • A pull over push pattern was introduced.
  28. SQPR-2 Medium Arbitrary Prize Distribution Arbitrary Control Acknowledged
    Location
    SquidBetPrizePool.sol: 71

    Description

    Anyone can call the function winnersClaimPrizePool and end the prize pool as long as some winners have been added.

    Recommendation

    Make sure to always add all winners at once.

    Resolution

    • The risk has been acknowledged.

More from UltiBets

  1. Protocol Review, Part 1

    38 findings3 critical · 11 high 38 findings: 3 critical, 11 high, 3 medium, 21 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