DECA

CertiKFiles named in the report

CertiK reviewed Fastex (DECA) on 18 Jan 2024: 29 findings, 0 critical; 23 marked fixed, 6 open.

No open critical findings
Open the reportOpen Fastex’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings29

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High202
Medium104
Low3017
HighFailed to create channel in ERC20 modeFixed

The creation of a channel by the user requires the `sent` parameter to be set to `true`. Nevertheless, in ERC20 mode, the `sent` parameter is always set to `false`, thereby making it impossible for the user to create a ERC20 channel. ```solidity=103 if (casino.isERC20()) { require(0 == msg.value, 'Error: You cannot deposit in ERC20 mode'); } else { (sent, ) = payable(addr).call{value: msg.value}(""); } require(sent, 'Error: Cannot deposit'); ```

HighMissing checks for bet amountFixed

There are no sanity checks that make sure the action[2] bet amount in the function argument is the actual asset amount player put into the channel.

Medium`Ownerprofit` not subtracted from the `balanceChFixed

In the current implementation of `sync()`, the `Ownerprofit` is not subtracted from the `partners[partnerId_].balanceChange`. As a result, if the owner invokes withdrawOwner before a partner withdraws, the partner may not be able to withdraw their entire assets using withdrawPartnerBank() due to an insufficient balance. ```solidity=255 function sync(uint32 partnerId_, int256 balanceChange_) external { require(partnerExists(partnerId_), 'Error: Partner with this ID does not exist'); if (partners[partnerId_].startTime + MONTH < block.timestamp) { if (0 < partners[partnerId_].balanceChange) { ownerBalance += uint256(partners[partnerId_].balanceChange) * partners[partnerId_].ownerProfitPercentage / 100; } partners[partnerId_].startTime = block.timestamp; partners[partnerId_].balance += partners[partnerId_].balanceChange; partners[partnerId_].balanceChange = 0; } partners[partnerId_].balanceChange += balanceChange_; } ```

MediumAnyone can change the balance of partners throughFixed

The function `sync()` can be called by anyone as it has no access restriction. This enables anyone to call this and change the balance of the partner. ```solidity=255 function sync(uint32 partnerId_, int256 balanceChange_) external { require(partnerExists(partnerId_), 'Error: Partner with this ID does not exist'); if (partners[partnerId_].startTime + MONTH < block.timestamp) { if (0 < partners[partnerId_].balanceChange) { ownerBalance += uint256(partners[partnerId_].balanceChange) * partners[partnerId_].ownerProfitPercentage / 100; } partners[partnerId_].startTime = block.timestamp; partners[partnerId_].balance += partners[partnerId_].balanceChange; partners[partnerId_].balanceChange = 0; } partners[partnerId_].balanceChange += balanceChange_; } ```

MediumDifferent slots games do not strictly validate `aFixed

In the `startAction()` function of the spin contract, the variable `balanceChange` is calculated by multiplying `action_[2]` with `action_[3]`, which represents the total bet amount. However, when inheriting the spin contract, the `UltraHot`, `BlazingHot`, `DiamondFlash`, `DiamondRush`, `LucyBell20`, `LosApaches`, and `MagicTreasuresOfEgypt` contracts calculate the win variable using only `state_[2]` instead of `state_[2] * state_[3]` in their respective `spinAction()` functions. This difference in the calculation may result in unexpected winning amounts if the value of `state_[3]` is not equal to 1. Furthermore, the `scatWin()` function utilizes `state_[2] * action_[3]`, while the `winCalculation()` function does not consider the multiplication factor. ```solidity=40 function startAction(uint256[] memory state_, uint256[] memory action_, address) virtual override internal view returns (int256, uint256[] memory) { ... int256 balanceChange = maxWin - int256(action_[2] * action_[3]); ... } ``` ```solidity=86 function spinAction(uint256[] memory state_, uint256[] memory rand_, address wheelAddress_) virtual override public view returns (int256, uint256[] memory) { ... win *= state_[2]; ... } ```

MediumThe first bet amount is lower than expectedFixed

As per the process definition, when a user places a bet using the `_startAction()` function, the casino initially transfers the winning amount to the channel. Subsequently, the `serverStep()` function facilitates the transfer of bets and winnings from the channel to the casino, resulting in the deduction of the user's bets. However, the `balanceChange` value returned by the `_startAction()` function corresponds to the bet amount, which is greater than the winnings. Thus, in the event of a loss, the user would end up losing less than expected. ```solidity=123 function _startAction(uint256[] memory state_, uint256[] memory action_) private view returns (int256, uint256[] memory) { assert(1 == action_[0]); require(3 == state_[0] || 0 == state_[0], 'Error: state[0] had wrong value'); int256 balanceChange = 0; require(action_[3] <= height, 'Error: action[3] should not be greater than height'); require(0 != action_[3], 'Error: action[3] should not be 0'); require(0 != action_[4], 'Error: action[4] should not be 0'); require(1 == action_[3] || state_[3] + 1 == action_[3], 'Error: incorrect line jump'); if (1 == action_[3]) { require(int256(action_[2] * action_[1]) <= maxBet, 'Error: bet cannot be more than max bet'); balanceChange = int256(action_[2] * action_[1]); state_[6] = 0; } else { require(state_[1] == action_[1], 'Error: Can not change game column count during game'); balanceChange = int256(state_[6] * state_[1]); } state_[0] = 1; // Change state to current state_[1] = action_[1]; state_[2] = (action_[2] * 95238) / 100000; state_[3] = action_[3]; state_[4] = action_[4]; return(balanceChange, state_); } ``` ```solidity=153 function _serverReply(uint256[] memory state_, uint256[] memory action_) private pure returns (int256, uint256[] memory) { assert(2 == action_[0]); require(1 == state_[0], 'Error: state[0] should not be 1'); require(state_[1] == action_[1], 'Error: state[1] should be equal to action[1]'); int256 balanceChange = 0; if (1 == state_[3]) { balanceChange = -int256((state_[1] + 1) * state_[2]); } else { balanceChange = -int256((state_[1] + 1) * state_[6]); } state_[0] = 2; // Change state to current return(balanceChange, state_); } ```

Low`partners[partnerId_].isLocked` Not CheckedFixed

The function `createChannel()` is called to create a new channel without checking whether `partnerId_` is locked or whether `casino` is locked.

LowAbstract contract `ChangingReelsFreeSpinScatterWiFixed

Abstract contracts are used as base contracts so that the child contract can inherit and utilize its functions, but contract `ChangingReelsFreeSpinScatterWildCard` is not inherited by other contracts.

LowIncompatibility With Deflationary TokensFixed

When transferring standard ERC20 deflationary tokens, the input amount may not be equal to the received amount due to the charged transaction fee. As a result, an inconsistency in the amount will occur and the transaction may fail due to the validation checks.

LowInconsistency between `minBet` and `maxBet` checkFixed

For `minBet`, the check condition is `action_[2] >= minBet`, which ensures that the value of `action_[2]` (presumably representing the bet amount) is greater than or equal to the minimum bet requirement specified by "minBet". This condition guarantees that the bet placed meets or exceeds the minimum amount set by the contract. On the other hand, for `maxBet`, the check condition is `(action_[2] * action_[3]) <= maxBet`, where `action_[2] * action_[3]` represents the total bet amount considering the number of hands played. This condition checks if the total bet amount is less than or equal to the maximum bet limit defined by `maxBet`. It ensures that the combined bet amount, taking into account the number of hands played (represented by `action_[3]`), does not exceed the maximum bet limit. In summary, `minBet` enforces a minimum bet requirement for each individual hand, while `maxBet` limits the total bet amount based on the number of hands played and the maximum bet restriction.

Show 10 more
LowInconsistent Balance CalculationFixed

In the `_startAction()` and `_serverReply()` functions, the balance is calculated by multiplying the bet by the coefficient. However, in the `getHiLoResult()` function, the calculation involves multiplying the bet by a coefficient and dividing the result by 100.

LowIncorrect Error MessageFixed

The `depositPartnerBank()` function is not allowed to deposit ERC20 tokens, while the `depositPartnerBankERC20()` function is designed specifically for that purpose. However, it's worth noting that the error messages generated by the `require(! isERC20(), 'Error: You can deposit only ERC20 tokens')` and `require(isERC20(), 'Error: Deposit of ERC20 tokens is not allowed')` are contradictory.

LowIncorrect winning amount due to divide truncationFixed

The truncation will occur when the `action_[2]` equals 1. If the user deposits a bet of 1, the `state_[2]` equals 0. However, in the `_finishAction()` function, the first calculation of the winning amount is based on `state_[2]`. Once the user selects the correct result and since "state_[2]" is equal to 0, the return value of the `getChoiceResult()` function will be 0 because the parameter `win` is also equal to 0. As a result, the user will be judged to have lost. ```solidity=123 function _startAction(uint256[] memory state_, uint256[] memory action_) private view returns (int256, uint256[] memory) { assert(1 == action_[0]); require(3 == state_[0] || 0 == state_[0], 'Error: state[0] had wrong value'); int256 balanceChange = 0; require(action_[3] <= height, 'Error: action[3] should not be greater than height'); require(0 != action_[3], 'Error: action[3] should not be 0'); require(0 != action_[4], 'Error: action[4] should not be 0'); require(1 == action_[3] || state_[3] + 1 == action_[3], 'Error: incorrect line jump'); if (1 == action_[3]) { require(int256(action_[2] * action_[1]) <= maxBet, 'Error: bet cannot be more than max bet'); balanceChange = int256(action_[2] * action_[1]); state_[6] = 0; } else { require(state_[1] == action_[1], 'Error: Can not change game column count during game'); balanceChange = int256(state_[6] * state_[1]); } state_[0] = 1; // Change state to current state_[1] = action_[1]; state_[2] = (action_[2] * 95238) / 100000; state_[3] = action_[3]; state_[4] = action_[4]; return(balanceChange, state_); } ``` ```solidity=47 function getChoiceResult(uint256 gameType_, uint256 choice_, uint256 lastWin_, uint256 rand_) public view returns (uint256) { require(gameSize > rand_, 'Error: rand too big'); if (choice_ == rand_ + 1) { return (gameType_ + 1) * lastWin_; } return 0; } ```

LowMalleability of `abi.encodePacked`Fixed

Encoding through `abi.encodePacked()` is used with dynamically-sized inputs controlled by the user. This type of encoding is susceptible to malleability when used with strings, bytes, or other dynamically-sized types. As such, it is not a good choice for signature verification with the input used, since users can change the input and potentially get the same bytes output from `abi.encodePacked`. ```solidity=496 return keccak256(abi.encodePacked('dev', gameAddress_, channelAddress_, gameState_, rand2_, balanceChange_, rand1Sha_, newGameNonce_)); ``` - `abi.encodePacked` is called with dynamically-sized arguments: `"dev"`, `gameState_`, `rand2_` ```solidity=487 uint256[] memory gameState_, ``` - `gameState_` is a dynamically-sized external parameter. ```solidity=488 uint256[] memory rand2_, ``` - `rand2_` is a dynamically-sized external parameter.

LowMissing Checks For `action_[4]` and `state_[doublFixed

In the `serverReply()` function, there is no check for `action_[4]` and `state_[doubleStateIndex]` being zero when the action is `spin`. Additionally, the `finishAction()` function also fails to check for `state_[doubleStateIndex]` being zero.

LowMissing Error MessagesFixed

The **require** can be used to check for conditions and throw an exception if the condition is not met. It is better to provide a string message containing details about the error that will be passed back to the caller.

LowNot divided by `wheel.winMultiply()`Fixed

Within the `spinAction()` function, the `win` variable is divided by `wheel.winMultiply()`. However, in the `getImageDetails()` function, the `retvals` variable is not divided by `wheel.winMultiply()`. If the `wheel.winMultiply()` is not equal to 1, the return values of `spinAction()` and `getImageDetails()` may not match.

LowPotential Front-Running RiskFixed

Malicious hackers may observe the pending transaction which will execute the `addWin/Line/Reel` function, and launch a similar transaction but with the hacker-picked Array to manipulate the gaming output. In the `deploy.js`, the `start()` function does the initialization of all game parameters and adds them to the casino's game list.

LowPotential inconsistency between `state_[4]` andFixed

The `state_[4]` equals to `win` in the linked code, but the `balanceChange` is `win / wheel.winMultiply()`. In the current setting file `UltraHot.json`, the `winMultiply()` is 1 and the `state_[4]` and `balanceChange` are equal, so this will not introduce problems. However, if the `winMultiply()` is not 1, the `balanceChange` may be less than `state_[4]` and the user will receive less winning amount. ```solidity=111 state_[4] = win; state_[doubleStateIndex] = 0; win /= wheel.winMultiply(); return (int256(win), state_); ```

LowPotential Reentrancy AttackFixed

A reentrancy attack can occur when the contract creates a function that makes an external call to another untrusted contract before resolving any effects. If the attacker can control the untrusted contract, they can make a recursive call back to the original function, repeating interactions that would have otherwise not run after the external call resolved the effects. In the linked code, `ownerBalance` is updated after the external call `payable(address_).call{value: amount_}("")`, which allows the `OWNER_ROLE` to re-enter the contract and make another `WithdrawOwner` call. This allows the `OWNER_ROLE` to drain the ETH balance within the casino contract. ```solidity=114 function withdrawOwner(address to_, uint256 amount_) external onlyRole(OWNER_ROLE) { require(ownerBalance >= amount_, 'Error: Insufficient balance'); require(_getbalance(address(this)) >= amount_, 'Error: Casino not allowed to withdraw that amount'); _transfer(to_, amount_); emit OwnerWithdrawed(to_, amount_); ownerBalance -= amount_; } ```

Show 3 more
LowState Variable Should Be Declared ConstantFixed

State variables that never change should be declared as `constant` to save gas. ```solidity=17 uint256 public height = 10; ``` - `height` should be declared `constant`. --- ```solidity=18 uint256 public gameSize = 3; ``` - `gameSize` should be declared `constant`.

LowThe Function `bonusAction()` Call Will FailFixed

The `bonusAction()` function is an empty function without any implementation. Once it becomes a game, calling `startAction()` function again after a round will result in an "Error: state[0] had wrong value" error message because `state_[0]` was not updated to `3` in the `finishAction()` function.

LowUninitialized State VariableFixed

One or more state variables are used without being initialized in the constructor. ```solidity=15 uint256 public wildCard; ``` - `wildCard` is never initialized, but used in `ChangingReelsFreeSpinScatterWildCard.spinAction`. --- ```solidity=16 uint256 public scatSymbol; ``` - `scatSymbol` is never initialized, but used in `ChangingReelsFreeSpinScatterWildCard.spinAction`.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (29)

  • contracts/games/BaseGame.sol
  • contracts/games/slots/ChangingReelsFreeSpinScatterWildCard.sol
  • contracts/games/slots/FreeSpin.sol
  • contracts/games/slots/FlamingFruits.sol
  • contracts/games/slots/LuckyBell20.sol
  • contracts/games/slots/Double.sol
  • contracts/games/HiLo.sol
  • contracts/games/slots/SlotLibrary.sol
  • contracts/games/slots/UltraHot.sol
  • contracts/games/slots/MagicTreasuresOfEgypt.sol
  • contracts/games/slots/Spin.sol
  • contracts/games/videoPokers/JacksOrBetter.sol
  • contracts/games/slots/BlazingHot.sol
  • contracts/FastChannelV3.sol
  • contracts/games/videoPokers/VideoPoker.sol
  • contracts/games/videoPokers/AllAmerican.sol
  • contracts/games/videoPokers/TensOrBetter.sol
  • contracts/games/slots/DiamondRush.sol
  • contracts/FastChannelV3Fabric.sol
  • contracts/games/slots/Wheel.sol
  • contracts/games/videoPokers/PokerHandUtils.sol
  • contracts/games/slots/DiamondFlash.sol
  • contracts/games/slots/BaseSlot.sol
  • contracts/games/slots/LosApaches.sol
  • contracts/games/slots/Bonus.sol
  • contracts/games/Ogwil.sol
  • contracts/Casino.sol
  • contracts/games/videoPokers/BonusPoker.sol
  • contracts/games/videoPokers/DoubleDoubleBonus.sol

Part of Fastex’s security record

Fastex
Audits4 audits by 2 firmsLast audit Jan 2024, by CertiK.
Critical findingsNo open critical findingsAcross the 4 reports, 1 critical finding was reported. It is marked fixed by the firm that reported it.
Open Fastex’s security record

Security firm

CertiK

1,044 projects reviewed · 1,375 reports · latest May 2025

See CertiK’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us