CertiK reviewed Qrkita (Qrkita Exchange) on 30 May 2023: 17 findings, 0 critical; 13 marked fixed, 2 acknowledged, 2 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 | 2 | 2 |
| Medium | 0 | 0 | 4 |
| Low | 2 | 0 | 7 |
```solidity=1043 // add the liquidity uniswapV2Router.addLiquidityETH{value: ethAmount}( address(this), tokenAmount, 0, // slippage is unavoidable 0, // slippage is unavoidable owner(), block.timestamp ); ``` The `addLiquidity` function calls the `uniswapV2Router.addLiquidityETH` function with the `to` address specified as `owner()` for acquiring the generated LP tokens from the `QRT-BNB` pool. As a result, over time the `_owner` address will accumulate a significant portion of LP tokens. If the `_owner` is an EOA (Externally Owned Account), mishandling of its private key can have devastating consequences to the project as a whole.
The `swapAndLiquify` function converts half of the `contractTokenBalance` QRT tokens to BNB. The other half of QRT tokens and part of the converted BNB are deposited into the QRT-BNB 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 QRT drops after swapping the first half of QRT tokens into BNBs, and the other half of QRT 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.
The following 4 functions set different fee rates for the 4 types of fee, and updates the corresponding "previous fee" variable. During the process, `_previousBurnFee` is updated to `_taxFee` instead of `_burnFee`. - `enableDefaultFees()` - `enableLowFees()` - `enableHighFees()` - `disableAllFees()` ```solidity=1133 function enableDefaultFees() external onlyOwner() { _taxFee = 12; _previousTaxFee = _taxFee; _liquidityFee = 5; _previousLiquidityFee = _liquidityFee; _burnFee = 4; _previousBurnFee = _taxFee; _marketingFee = 3; // Marketing fee is to energize the token development and also a buyback & burn program. _previousmarketingFee = _marketingFee; inSwapAndLiquify = true; emit SwapAndLiquifyEnabledUpdated(true); } ```
In function `_tokenTransfer()`, if the sender or receiver is excluded from fees, the code will call `removeAllFee()` to set all the fee to 0 before the transfer. When token transfer finishes, the code will call `restoreAllFee()` to set the fees back. Function `restoreAllFee()` does not restore the fees from the 4 "previous fees"; instead, it updates the fee to a fixed value. This set of values seem to be the default rate, as it matches the setting in function `enableDefaultFees()`. This could be a problem because the contract also allows for other sets of fee rates. Once an excluded account makes/receives a transfer, the fee rate will be reset to default, whatever the previous fee rate. ```solidity=949 function restoreAllFee() private { _taxFee = 12; _liquidityFee = 5; _burnFee = 4; _marketingFee = 3; } ```
Generally, deflationary tokens are incompatible with DEX and special rules need to be coded for DEX addresses eg. excluding them from reward. Otherwise, a hacker can exploit the protocol using the reflection mechanism.
Contract `QrkitaToken` introduces `_burnFee` and `_marketingFee` along with the tax fee and liquidity fee. In function `_tokenTransfer`, the corresponding percentage of burn fee and marketing fee is deducted from the total transfer amount. Marketing fee is then transferred to `marketingWallet` without fees. On the other hand, no action is taken on burn fee, and it'll stay in the sender's wallet. For example, for a `_burnFee` of 4, only 96% of the requested transfer amount will be transferred out to the recepient or collected as fees; the remaining 4% will stay in the sender's wallet.
At the beginning of the contract, the comment describes a 4% fee, which does not match with the code. ```solidity=3 /** Website: https://qrkita.exchange # qrkita features: ⤏ 1% fee distributed to all holders every transaction ⤏ 1% fee auto added to the liquidity pool every transaction ⤏ 1% Partnerships wallet ⤏ 1% Charity Wallet */ ```
There are several typos in the code and comments. 1. Event `SwapAndLiquify`: `tokensIntoLiqudity` should be `tokensIntoLiquidity`. ```solidity=724 event SwapAndLiquify( uint256 tokensSwapped, uint256 ethReceived, uint256 tokensIntoLiqudity ); ``` 2. Comment on line 876: `recieve` should be `receive` and `swaping` should be `swapping`. ```solidity=876 //to recieve ETH from uniswapV2Router when swaping ``` 3. Function on line 455: `geUnlockTime()` should be `getUnlockTime()`. ```solidity=455 function geUnlockTime() public view returns (uint256) { return _lockTime; } ``` 4. Require statement on line 851: "Account is already excluded" should be "Account is already included". ```solidity=850 function includeInReward(address account) external onlyOwner() { require(_isExcluded[account], "Account is already excluded"); ... ```
An owner is possible to gain ownership of the contract even if they calls function `renounceOwnership` to renounce the ownership. This can be achieved by performing the following operations: 1. Call `lock` to lock the contract. The variable `_previousOwner` is set to the current owner. 2. Call `unlock` to unlock the contract. 3. Call `renounceOwnership` to leave the contract without an owner. 4. Call `unlock` to regain ownership.
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()` - `uniswapV2Router.addLiquidityETH()`
In function `_tokenTransfer`, the condition `!_isExcluded[sender] && !_isExcluded[recipient]` can be included in `else` . ```solidity=1067 if (_isExcluded[sender] && !_isExcluded[recipient]) { _transferFromExcluded(sender, recipient, (amount.sub(burnAmt).sub(marketingAmt))); } else if (!_isExcluded[sender] && _isExcluded[recipient]) { _transferToExcluded(sender, recipient, (amount.sub(burnAmt).sub(marketingAmt))); } else if (!_isExcluded[sender] && !_isExcluded[recipient]) { _transferStandard(sender, recipient, (amount.sub(burnAmt).sub(marketingAmt))); } else if (_isExcluded[sender] && _isExcluded[recipient]) { _transferBothExcluded(sender, recipient, (amount.sub(burnAmt).sub(marketingAmt))); } else { _transferStandard(sender, recipient, (amount.sub(burnAmt).sub(marketingAmt))); } ```
```solidity function includeInReward(address account) external onlyOwner() { require(_isExcluded[account], "Account is already included"); for (uint256 i = 0; i < _excluded.length; i++) { if (_excluded[i] == account) { _excluded[i] = _excluded[_excluded.length - 1]; _tOwned[account] = 0; _isExcluded[account] = false; _excluded.pop(); break; } } } ``` Variable `_rOwned[account]` is not updated in the function `includeInReward()`, which will make the accounts included siphon off the tokens out of the balances of all token holders. The Rate was higher at the moment of the `excludeFromReward(account)` call, so the `_rOwned[account] / _tOwned[account]` ratio is bigger than expected for accounts included in the reward.
The following variables could be declared as `constant` since these state variables are never to be changed. A big advantage of constant variables is that reading them is significantly cheaper than reading from regular state variables since will not be stored in storage. Still, values will directly insert the values into the runtime code. - `_tTotal` - `numTokensSellToAddToLiquidity` - `_name` - `_symbol` - `_decimals`
| Address | Chain | Source code |
|---|---|---|
| 0x72b6…f73d0x72b6887a46043c1299d3dae707893123d47cf73d | Chain: BNB Chain | Source code: Verified source |
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