CertiK reviewed Glow Token V2 on 3 Aug 2022: 29 findings, 1 critical; 8 marked fixed, 7 acknowledged, 14 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 | 1 | 0 | 0 |
| High | 0 | 4 | 5 |
| Medium | 0 | 3 | 1 |
| Low | 13 | 0 | 2 |
```solidity=292 bool prevExclusion = _excludeFromFees[account]; // ensure we don't remove exclusions if the current wallet is already excluded _excludeFromFees[msg.sender] = true; //try to swap for tokens, if it fails (bad contract, or whatever other reason, send BNB) try _pancakeRouter.swapExactETHForTokensSupportingFeeOnTransferTokens{value: amountWei}( 0, path, account, block.timestamp ){buyNoFeesSuccess = true;} catch {buyNoFeesSuccess = false;} _excludeFromFees[msg.sender] = prevExclusion; // set value to match original value ``` The `msg.sender` is mistakenly set as `_excludeFromFees[msg.sender] = true` and reset as `prevExclusion`. Correct way is to exclude `account` from fees and reset.
The function `_swapBNBtoRWRD` is used for sending lottery tokens or distributing reward tokens. If failing to send `RWRD`, it will lead to send BNB directly. If BNB also fails to be sent. The `_holders[account].alreadyPaid` will be reset. However, the `_holders[account].alreadyPaid` may be less than `amountWei`, which will cause the reversion of transaction. Besides, the `_holders[account].alreadyPaid` is only used for calculating reward, which means handling exception in this way does not fit the case of failing to send lottery tokens. ```solidity // if the swap failed, send them their BNB instead if(!buyNoFeesSuccess){(bool success,) = account.call{value: amountWei, gas: 3000}(""); if(!success) {_holders[account].alreadyPaid = _holders[account].alreadyPaid-(amountWei);} } ```
The mechanism of calculating the unpaid reward is wrong. At the same time of someone holding the tokens, he already has his unpaid reward, which seems the reward pays no attention to the holding time. The increased reward should only be used to reward those who hold tokens before the reward been produced. And if someone sells all his tokens, his unpaid reward will be zero even he have not received his reward yet, which is also unfair. What's more, the `profitPerToken` only changed when the reward being produced. If someone buys tokens after that, he also could get unpaid reward, which is `amount * profitPerToken`. Since the `profitPerToken` does not change according to the new holder's participation, his unpaid reward is actually much more than what he deserves. ```solidity function _getUnpaidReward(address account) private view returns (uint256) { if(_holders[account].rewardsAmount == 0){ return 0; } uint256 toBePaid = _calculateRewards(account,_holders[account].rewardsAmount); uint256 alreadyPaid = _holders[account].alreadyPaid; if(toBePaid <= alreadyPaid){ return 0; } return toBePaid-(alreadyPaid); } function _calculateRewards(address account,uint256 amount) private view returns (uint256) { return _excludeFromRewards[account]?0:amount*_tracker.profitPerToken/(2**64); } ```
The function `ownerFixStuckBNB` moved the other BNB to `totalMarketingBNB`. However, `totalMarketingBNB` has been increased but the `totalLotteryBNB` and `totalRewardBNB` have not been decreased, which may cause problems. ```solidity function ownerFixStuckBNB() public onlyOwner { uint256 stuckBNB=address(this).balance-(_tracker.totalMarketingBNB+_tracker.totalCharityBNB); _tracker.totalMarketingBNB+=(stuckBNB>0?stuckBNB:0); emit OwnerFixStuckBNB(stuckBNB); } ```
The initial `_holders[account].toBePaid` is zero. When updating it in function `_distributeRewards`, it is still zero. Since it is always zero, there will be no chance to remove share holder in function `_setShareholder`. ```solidity function _setShareholder(address account, uint256 amount) private { if(amount > 0 && _holders[account].rewardsAmount == 0){ _addShareholder(account); } if(amount == 0 && _holders[account].toBePaid > 0){ _removeShareholder(account); } _tracker.totalTokensHeld = _tracker.totalTokensHeld-(_holders[account].rewardsAmount)+(amount); _holders[account].rewardsAmount = amount; } ... function _distributeRewards(address account) private { if(_holders[account].rewardsAmount == 0){ return; } uint256 amountWei = _getUnpaidReward(account); if(amountWei > 0){ _tracker.totalRewardBNBPayout+=amountWei; _holders[account].lastClaim = block.timestamp; _holders[account].alreadyPaid+=amountWei; _holders[account].toBePaid=_calculateRewards(account,_holders[account].rewardsAmount); _tracker.totalRewardBNB-=amountWei; _swapBNBtoRWRD(account, amountWei); } } ```
```solidity uint256 rewardBNB=amountWei*_tax.marketingTax/100; ``` The `rewardBNB` is miscalculated by using the `marketingTax`.
```solidity function _distributeBNB(uint256 amountWei) private { uint256 charityBNB=amountWei*_tax.charityTax/100; uint256 lotteryBNB=amountWei*_tax.lotteryTax/100; uint256 rewardBNB=amountWei*_tax.marketingTax/100; uint256 marketingBNB=amountWei-(charityBNB+lotteryBNB+rewardBNB); if(_tracker.totalTokensHeld==0)_tracker.totalMarketingBNB+=(charityBNB+lotteryBNB+rewardBNB+marketingBNB); ... ``` The `charityBNB+lotteryBNB+rewardBNB+marketingBNB` is `amountWei`. There is no need to calculate again.
```solidity=669 _tracker.totalMarketingBNB+=(stuckBNB>0?stuckBNB:0); ``` There is no need to check if `uint256 stuckBNB` is below zero or not.
| Address | Chain | Source code |
|---|---|---|
| 0xef37…830c0xef3725d9e4b9d758483c86be917ab7321dc2830c | 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