CertiK reviewed XPROJECT (Xpro) on 9 Jun 2023: 21 findings, 0 critical; 7 marked fixed, 3 acknowledged, 11 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 | 0 | 1 | 2 |
| Medium | 0 | 2 | 2 |
| Low | 11 | 0 | 3 |
The `swapAndLiquify()` function converts half of the `contractTokenBalance` `XPRO` tokens to BNB. The other half of `XPRO` tokens and part of the converted BNB are deposited into the `XPRO-WBNB` pool on PancakeSwap as liquidity. For every `swapAndLiquify()` function call, a small amount of BNB leftover in the contract. This is because the price of `XPRO` drops after swapping the first half of `XPRO` tokens into BNBs, and the other half of `XPRO` tokens require less than the converted BNB to be paired with it when adding liquidity. The contract doesn't appear to provide a way to withdraw those BNB, and they will be locked in the contract forever.
There should always be events emitted in the sensitive functions that are controlled by centralization roles.
In many cases, liquidity fees are accumulated in a contract until a certain minimum amount is reached, at which point they are added to the liquidity pool. However, in such cases, there is typically no oracle used, and slippage cannot be avoided. This creates a vulnerability that can be exploited through a flashloan attack, provided that the attacker can obtain a loan through a protocol other than the LP pair used by the contract. For example, if a contract uses a PancakeSwap LP pair with BNB, an attacker could borrow a large amount of the token from a different protocol and then use PancakeSwap to swap all of these tokens for BNB. This would cause the price of BNB in the pair to be higher than expected. The attacker could then transfer some token to the contract to trigger the `swapAndLiquify()` function, which would add liquidity with the BNB being overvalued. The attacker could then swap the BNB back to the original token and receive more tokens than they started with, due to the additional liquidity added with BNB overvalued. The loan plus interest would then be paid back, and any other tokens could be sold for a profit. In the `XPRO` contract, it works in this way: 1. On each normal `transfer()`, 2% of the amount is taken as `liquidityFee` and stored on `address(this)` 2. If `balanceOf(this)` is bigger than `numTokensSellToAddToLiquidity` ($ 3 \times 10^{16}$), the `swapAndLiquify(contractTokenBalance)` is called 3. During that half is converted into BNB via call to `uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens()` 4. Then `uniswapV2Router.addLiquidityETH()` is called with half XPRO stored and the converted amount of BNB 5. No price Oracle is used, slippage is not controlled This allows the attacker to steal a part of `balanceOf(this)` via a flashloan attack.
A sandwich attack might happen when an attacker observes a transaction swapping tokens or adding liquidity without setting restrictions on slippage or minimum output amount. The attacker can manipulate the exchange rate by frontrunning (before the transaction being attacked) a transaction to purchase one of the assets and make profits by backrunning (after the transaction being attacked) a transaction to sell the asset. The following functions are called without setting restrictions on slippage or minimum output amount, so transactions triggering these functions are vulnerable to sandwich attacks, especially when the input amount is large: - `uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens()` ```solidity=696 uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens( tokenAmount, 0, path, address(this), block.timestamp ); ``` --- - `uniswapV2Router.addLiquidityETH{value: ethAmount}()` ```solidity=706 uniswapV2Router.addLiquidityETH{value: ethAmount}( address(this), tokenAmount, 0, 0, owner(), block.timestamp ); ``` For example, when we want to make a transaction of swapping 100 `XPRO` for 1 BNB, an attacker could raise the price of BNB by adding `XPRO` into the pool before the transaction so we might only get 0.1 BNB. After the transaction, the attacker would be able to withdraw more than he deposited because the total value of the pool increases by 0.9 BNB.
The `else if` condition `!_isExcluded[sender] && !_isExcluded[recipient]` in Line#722 can be included in `else` . ```solidity=718 if (_isExcluded[sender] && !_isExcluded[recipient]) { _transferFromExcluded(sender, recipient, amount); } else if (!_isExcluded[sender] && _isExcluded[recipient]) { _transferToExcluded(sender, recipient, amount); } else if (!_isExcluded[sender] && !_isExcluded[recipient]) { _transferStandard(sender, recipient, amount); } else if (_isExcluded[sender] && _isExcluded[recipient]) { _transferBothExcluded(sender, recipient, amount); } else { _transferStandard(sender, recipient, amount); } ```
Solidity version >=0.8.0 includes checked arithmetic operations and underflow/overflow by default, making SafeMath redundant.
State variables that never change should be declared as `constant` to save gas. ```solidity=393 string private _name = "XProject"; ``` - `_name` should be declared `constant`. --- ```solidity=394 string private _symbol = "XPRO"; ``` - `_symbol` should be declared `constant`. --- ```solidity=395 uint8 private _decimals = 9; ``` - `_decimals` should be declared `constant`.
Literals with many digits are difficult to read and review.
The typo in the following code is the misspelling of "liquidity" in the `tokensIntoLiqudity` parameter. It should be corrected to `tokensIntoLiquidity` to accurately reflect the meaning of the variable and avoid any potential confusion. ```solidity=415 event SwapAndLiquify( uint256 tokensSwapped, uint256 ethReceived, uint256 tokensIntoLiqudity ); ```
The contract `XPRO` cited has an unlocked compiler version `^0.8.4`. An unlocked compiler version in the source code of the contract permits the user to compile it at or above a particular version. This, in turn, leads to differences in the generated bytecode between compilations due to differing compiler version numbers. This can lead to ambiguity when debugging, as compiler specific bugs may occur in the codebase that would be hard to identify over a span of multiple compiler versions rather than a specific one.
The following event is defined in the codebase but is not utilized. ```solidity=413 event MinTokensBeforeSwapUpdated(uint256 minTokensBeforeSwap); ```
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (1)
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