CertiK reviewed Fastex (DECA) on 18 Jan 2024: 29 findings, 0 critical; 23 marked fixed, 6 open.
Report hosted on skynet.certik.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 2 | 0 | 2 |
| Medium | 1 | 0 | 4 |
| Low | 3 | 0 | 17 |
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'); ```
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.
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_; } ```
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_; } ```
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]; ... } ```
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_); } ```
The function `createChannel()` is called to create a new channel without checking whether `partnerId_` is locked or whether `casino` is locked.
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.
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.
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.
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.
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.
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; } ```
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.
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.
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.
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.
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.
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_); ```
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_; } ```
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`.
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.
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`.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (29)
1,044 projects reviewed · 1,375 reports · latest May 2025
See CertiK’s auditsSomething 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