CertiK reviewed Shrub (Shrub token) on 29 Nov 2024: 12 findings, 0 critical; 1 marked fixed, 10 acknowledged, 1 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 | 1 |
| Medium | 0 | 1 | 0 |
| Low | 0 | 9 | 0 |
The function contains a flaw in its implementation where it is supposed to blacklist a particular address by marking it as true in a blacklist mapping. However, due to a logic error, the flag is incorrectly set to false, leading to the address not being blacklisted as intended when a disallowed transfer action is performed. ```solidity // anti bot logic if ( block.number <= (launchedAt + 0) && to != uniswapV2Pair && to != address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D) ) { @> _blacklist[to] = false; } ```
The `enableTrading()` function is designed to be called by the owner to activate trading. However, this function can be invoked multiple times, with each invocation updating the `launchedAt` variable to the current `block.number`. This behavior may interfere with the proper functioning of the blacklist mechanism. ```solidity // once enabled, can never be turned off function enableTrading() external onlyOwner { tradingActive = true; swapEnabled = true; launchedAt = block.number; } ```
The deployed token contract includes a mechanism to swap tokens for ETH when specific conditions are satisfied. However, with fees set to 0 and ownership renounced, the swap operation is blocked by the following condition in the `swapBack()` function: ```solidity if (contractBalance == 0 || totalTokensToSwap == 0) { return; } ``` As a result, the contract currently holds approximately 7,823,630 tokens that cannot be swapped for ETH, leaving the tokens effectively stuck. Furthermore, approximately 4.464 ETH is also locked within the token contract.
The `swapBack()` function has an issue with token balance management, which may lead to incorrect calculations during swap operations. Specifically, not all tokens are transferred, leaving `liquidityTokens` in the contract. Despite this, the internal counters `tokensForLiquidity`, `tokensForMarketing`, and `tokensForDev` are reset to zero, without accounting for the remaining `liquidityTokens`. This premature reset may result in incorrect token accounting if fees are recalibrated immediately afterward. Furthermore, the contract lacks a mechanism to add liquidity, even though `liquidityTokens` are calculated and exist within the function. This oversight indicates a missing implementation step for adding liquidity to the liquidity pool.
The smart contract contains low-level calls such as `call()`. Since these calls bypass some of the automatic checks that Solidity provides, like function type checks, they can introduce vulnerabilities, logic errors, or unexpected behavior. ```solidity=1540 (success, ) = address(devWallet).call{value: ethForDev}(""); ``` ```solidity=1542 (success, ) = address(marketingWallet).call{ value: address(this).balance }(""); ```
The transaction may be charged fees, so the max holding of the receiver should be the sum of the held tokens and the amount received from the transfer. The fees should not be included in the max holding. ```solidity=1421 require( amount + balanceOf(to) <= maxWallet, "Max wallet exceeded" ); ``` - Max holding is checked. ```solidity=1486 amount -= fees; ``` - Receiving amount is adjusted. ```solidity=483 _balances[recipient] = _balances[recipient].add(amount); ``` - Balance is increased by the adjusted amount.
There should always be events emitted in the sensitive functions that are controlled by centralization roles.
Addresses are not validated before assignment or external calls, potentially allowing the use of zero addresses and leading to unexpected behavior or vulnerabilities. For example, transferring tokens to a zero address can result in a permanent loss of those tokens. ```solidity=1341 devWallet = newWallet; ``` - `newWallet` is not zero-checked before being used. ```solidity=1336 marketingWallet = newMarketingWallet; ``` - `newMarketingWallet` is not zero-checked before being used.
A sandwich attack can occur when an attacker notices a transaction involving token swaps or liquidity additions that lacks slippage restrictions or minimum output amount settings. The attacker can exploit this by frontrunning the transaction to purchase one of the assets, thereby manipulating the exchange rate, and then backrunning the transaction to sell the asset for a profit. ```solidity=1499 uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens( tokenAmount, @> 0, // accept any amount of ETH path, address(this), block.timestamp ); ``` The referenced functions are invoked without setting slippage restrictions or minimum output amounts, making transactions that trigger these functions particularly vulnerable to sandwich attacks, especially when large input amounts are involved.
Some state variables are not used in the codebase. This can lead to incomplete functionality or potential vulnerabilities if these variables are expected to be utilized. Variable `_holderFirstBuyTimestamp` in `Contract` is never used in `Contract`. ```solidity=1076 mapping(address => uint256) private _holderFirstBuyTimestamp; ``` ```solidity=1052 contract Contract is ERC20, Ownable { ```
| Address | Chain | Source code |
|---|---|---|
| 0x3b99…c2560x3b991130eae3cca364406d718da22fa1c3e7c256 | Chain: Ethereum | 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