CertiK reviewed Sell Token (selltoken) on 30 Mar 2023: 16 findings, 0 critical; 12 marked fixed, 4 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 | 1 | 0 | 0 |
| Medium | 0 | 0 | 1 |
| Low | 3 | 0 | 11 |
In the `ShortStart()` function, when the user's bnb is greater than 10% of the pool balance, he cannot call `ShortStart()` for more amount. However, the balance of a specific coin can change, due to the withdrawal from other users. So when he calls `withdraw()`, his balance might be greater than 1-% of the pool balance at that moment and the below restriction is incorrect. ```solidity=498 if(getTokens >= tokens){ mkt.sell(token,Short[_msgSender()][token].token,tokens,_msgSender()); mkt.setPools(token,tokens,false); }else { mkt.sell(token,Short[_msgSender()][token].token,getTokens,_msgSender()); mkt.setPools(token,getTokens,false); } ```
We would like to know what is the purpose of the below logic: ```solidity=455 if(mkt.balanceOf(coin) <=0) return ; ``` This could prevent the first user to call `ShortStart()`.
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 example, if a user sends 100 deflationary tokens (with a 10% transaction fee) to the target contract, only 90 tokens actually arrive at the contract. In the function `Minerals.buy()`, the swap accepts at most 5% of slippage. The below logic does not consider both deflationary loss and swap slippage loss, and still record the min as the received amount. ```solidity=283 IRouter(_router).swapExactETHForTokensSupportingFeeOnTransferTokens{value: amount0In}(min*95/100,path,address(this),block.timestamp); if(_token!=_USDT){ balanceOf[_token]+=min; } } ```
In the function `setPool()`, when the input `isPool` is not zero, the logic is as below: ```solidity=374 }else{ require(msg.value>=fee); IERC20(token).transferFrom(_msgSender(),address(this),coin); balanceOf[token]+=coin; balanceOfLook[token]+=coin; uint oldCoin=IERC20(_TRDT).balanceOf(address(this)); if(msg.value>0){ buy(_WBNB,address(_TRDT),msg.value); if(IERC20(_TRDT).balanceOf(address(this))>oldCoin){ uint ut=IERC20(_TRDT).balanceOf(address(this))-oldCoin; balanceOfLook[_TRDT]+=ut; } } if(pair[token]==address(0)){ pair[token]=token1; } } ``` The caller will transfer in a specific type of token, which is not recorded for the caller. The caller will also transfer in some bnb and these bnb will be used to buy _TRDT token. This will also not be recorded for the caller. Only the balanceOf/balanceOfLook will be updated.
The function should be only accessible for the ceo contract.
In the function `ShortStart()`, the value of `bnborUsdt` is retrieved from the Minerals.getPair() function. There is no check to ensure the `bnborUsdt` must be either _WBNB or _USDT. There is only one check to ensure it's not a zero address. In the following get price logic, if the `bnborUsdt` is not _WBNB, it will be treated as _USDT anyway. The admin should carefully manage the setup of pair info.
In the `setPool()` function, the caller will transfer a specific type of token, which is decided by the parameter he inputs. And he will get staking rewards after some time for the same type of tokens. However, this type of token might not be one of the tokens in the pair info, then there is no resource for the rewards. And he will be unable to withdraw.
A sandwich attack may 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 (executing before the target) a transaction to purchase one of the assets and make profits by backrunning (executing after the target) 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: ```solidity function buy(address bnbOrUsdt,address _token,uint amount0In) public{ ... IRouter(_router).swapExactTokensForTokens(amount0In,0,path,address(this),block.timestamp); ... } ```
The function Minerals.setPools() can only be called by the ceo, which should be the SellToken contract. In the SellToken contract, the all calls of `setPools()` are as below and the last parameter are false. ```solidity if(getTokens >= tokens){ mkt.sell(token,Short[_msgSender()][token].token,tokens,_msgSender()); mkt.setPools(token,tokens,false); }else { mkt.sell(token,Short[_msgSender()][token].token,getTokens,_msgSender()); mkt.setPools(token,getTokens,false); } ``` Hence the logic inside the case `zt is true` is redundant. ```solidity=263 function setPools(address token,uint value,bool zt)public{ require(_msgSender()==ceo,"You are not a routing contract administrator"); if(zt){ balanceOf[token]+=value; }else{ if(balanceOf[token] > value){ balanceOf[token]-=value; }else{ balanceOf[token]=0; } } } ```
The below logic in the `getMyPrice()` function is the same in different cases. ```solidity=600 if(nowPrice < Short[_msgSender()][token].tokenPrice){ zt=nowPrice * 1 ether / Short[_msgSender()][token].tokenPrice; }else { zt=nowPrice * 1 ether / Short[_msgSender()][token].tokenPrice; } ```
The function `getMyPriceSell()` can be used to view other users balance in the pool, without any authorizations.
The return value of the transfer()/transferFrom() call is not checked. ```solidity=339 IERC20(token).transfer(_msgSender(),value); ``` --- ```solidity=357 IERC20(token).transferFrom(_msgSender(),address(this),coin); ``` --- ```solidity=376 IERC20(token).transferFrom(_msgSender(),address(this),coin); ``` --- ```solidity=406 IERC20(token).transfer(owner(),_token); ```
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