Layerium - Audit

CertiK1 address on BNB Chain

CertiK reviewed Layerium (Layerium - Audit) on 4 Jul 2023: 19 findings, 0 critical; 6 acknowledged, 13 open.

No open critical findings
Open the reportOpen Layerium’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings19

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High030
Medium030
Low1300
LowDesign of swap intervalsOpen

The current implementation uses the variable `swapTrigger` as the transaction count threshold for making token swaps to BNB through the liquidity pool. It is noted that the current settings are relatively low, with swaps occurring every 3 transactions since `uint8 private swapTrigger = 3;`. Depending on the amount of fees collected from transfers and the number of transactions made using `LYUM`, such settings might lead to wasted gas due to the swapping of small amounts of tokens to WBNB. Although the current `swapTrigger` can be set to a higher value, it might be worth considering implementing a mechanism to instead check the for the balance of tokens collected in fees instead, like most other implementations do, to guarantee that only an amount above a certain amount of funds will be processed in swaps to save on gas fees. The audit team would like to check with the team if this implementation is intended.

LowImproper tracking of funds related to feesOpen

In the current design, the function `_transfer()` takes the entire token balance and proceeds to swap this balance into BNB through function `swapAndLiquify()`. ```solidity=825 if( txCount >= swapTrigger && !inSwapAndLiquify && from != uniswapV2Pair && swapAndLiquifyEnabled ) { txCount = 0; uint256 contractTokenBalance = balanceOf(address(this)); // Total token balance is used if(contractTokenBalance > _maxTxAmount) {contractTokenBalance = _maxTxAmount;} if(contractTokenBalance > 0){ swapAndLiquify(contractTokenBalance); } ``` The function `swapAndLiquify()` then takes the entire BNB balance and sends it to the `Wallet_Dev` address. ```solidity=881 function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap { swapTokensForBNB(contractTokenBalance); uint256 contractBNB = address(this).balance; // Total BNB balance is used sendToWallet(Wallet_Dev,contractBNB); } ``` The function `process_Tokens_Now()` also makes use of the same logic when processing swaps of the tokens to BNB since it also uses the total token balance as the base for its calculations. ```solidity=890 function process_Tokens_Now (uint256 percent_Of_Tokens_To_Process) public onlyOwner { // Do not trigger if already in swap require(!inSwapAndLiquify, "Currently processing, try later."); if (percent_Of_Tokens_To_Process > 100){percent_Of_Tokens_To_Process == 100;} uint256 tokensOnContract = balanceOf(address(this)); // Total token balance is used uint256 sendTokens = tokensOnContract*percent_Of_Tokens_To_Process/100; swapAndLiquify(sendTokens); } ``` In the event that there are tokens or BNB that are transferred into the contract, most often by accident, these funds will be mixed in with the funds related to fees since the entire balance is processed through the swaps and transferred out to the fee wallet `Wallet_Dev`.

LowInsufficient checks for pair and router address uOpen

The function `set_New_Router_and_Make_Pair()` does not include a check to see if the pair has already been created. As a result, calling createPair() on the factory contract will always cause this function to revert, preventing the update of the router to that address. Additionally, the functions `set_New_Router_Address` and `set_New_Pair_Address`, which solely update the router or pair address, do not ensure that the given router and pair will be compatible with each other. Moreover, changing the router and pair addresses may also impact the fee charging mechanism. The current logic only supports a single address for the router or pair, so if multiple pairs or routers exist, it may lead to unexpected fee charging behavior.

LowLack of mechanism to add liquidity to liquidity pOpen

The `Layerium` contract relies on `LYUM-WBNB` trading pair for swapping. In the function `swapAndLiquify()`, the `VZO` balance of this contract is swapped directly for `WBNB` which is then transferred to the `Wallet_Dev` wallet address. The function `swapAndLiquify()` has an external call to the function `swapTokensForBNB()`. ```solidity=881 function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap { swapTokensForBNB(contractTokenBalance); uint256 contractBNB = address(this).balance; sendToWallet(Wallet_Dev,contractBNB); } ``` The function `swapTokensForBNB()` makes the swap for `WBNB` through the `uniswapV2Router`. ```solidity=901 function swapTokensForBNB(uint256 tokenAmount) private { address[] memory path = new address[](2); path[0] = address(this); path[1] = uniswapV2Router.WETH(); _approve(address(this), address(uniswapV2Router), tokenAmount); uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens( tokenAmount, 0, path, address(this), block.timestamp ); } ``` There is no logic to use any of the funds to add liquidity to the liquidity pool. This can lead to risks related to price instability and price manipulation when there is insufficient liquidity in the pair liquidity pool. The only way of adding liquidity is to manually inject liquidity into the DEX liquidity pool. The audit team would like to check if this implementation matches the intended design and to understand whether the project has measures in mind to manage the liquidity in the token liquidity pool.

LowLack of tracking for failed blacklist updatesOpen

In functions `blacklist_Add_Wallets()` and `blacklist_Remove_Wallets()` there is a loop used to insert or remove addresses from the blacklist. The current implementation will compare the gas used to the gas left to ensure that there is still gas left to process the update to the `_isBlacklisted` mapping. In the case that large amounts of addresses are being processed, leading to a situation where gas has run out before the list can be completely updated, the function will simply stop updating the `_isBlacklisted` mapping. The following is an example of the loop from function `blacklist_Add_Wallets()`. ```solidity=648 for (uint256 i; i < addresses.length; ++i) { if(gasUsed < gasleft()) { startGas = gasleft(); if(!_isBlacklisted[addresses[i]]){ _isBlacklisted[addresses[i]] = true;} gasUsed = startGas - gasleft(); } ``` In this case, there will be no method to track which addresses in the list have been updated and which have not, nor will there be any way to know if the list actually completely updated without manual checks afterwards.

LowLack of updates for admin settingsOpen

Currently when the `Wallet_Dev` address is updated in function `Wallet_Update_Dev()` the old `Wallet_Dev` address is still included in the `_isExcludedFromFee` mapping even after being replaced by a new `Wallet_Dev` address. ```solidity=602 function Wallet_Update_Dev(address payable wallet) public onlyOwner() { Wallet_Dev = wallet; _isExcludedFromFee[Wallet_Dev] = true; } ``` It is important to note that this situation could also potentially occur when ownership is transferred to a new owner as well. The old owner must be manually removed from the exclusion and the new owner must be manually added to the fee exclusion.

LowMiscalculation of Max Wallet Holdings And Max traOpen

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 after fees are deducted. The fees should not be included in the max holding. The max holding is checked in the beginning of the `_transfer()` function ```solidity=793 require((heldTokens + amount) <= _maxWalletToken,"You are trying to buy too many tokens. You have reached the limit for one wallet.");} ``` Eventually a call is made to `_getValues()` in the function `_transferTokens()`. Fees are deducted and returned as the amount `tTransferAmount`. ```solidity=979 (uint256 tTransferAmount, uint256 tDev) = _getValues(tAmount); ``` In the function `_transferTokens()` the balance of the `recipient` is ultimately increased by the value `tTransferAmount` which is the amount adjusted after fees are deducted. ```solidity=981 _tOwned[recipient] = _tOwned[recipient].add(tTransferAmount); ``` There might be cases where the addition of the amount adjusted after the deduction of fees might not exceed the `_maxWalletToken`, but because the checking is applied before fees are deducted, then transfers that might otherwise be valid will be rejected. The same situation can be applied to the `_maxTxAmount` where the max transaction amount being transferred between wallets is also subject to fees. Therefore, the `tTransferAmount` after fees should be compared to the `_maxTxAmount` threshold instead of the entire unadjusted `amount`.

LowMissing Zero Address ValidationOpen

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=603 Wallet_Dev = wallet; ``` - `wallet` is not zero-checked before being used. --- ```solidity=953 uniswapV2Pair = newPair; ``` - `newPair` is not zero-checked before being used.

LowPotential Sandwich AttacksOpen

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: - `swapTokensForBNB()` ```solidity=901 function swapTokensForBNB(uint256 tokenAmount) private { address[] memory path = new address[](2); path[0] = address(this); path[1] = uniswapV2Router.WETH(); _approve(address(this), address(uniswapV2Router), tokenAmount); uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens( tokenAmount, 0, // Potential sandwich attack due to lack of minimum output amount path, address(this), block.timestamp ); } ```

LowState Variable Should Be Declared ConstantOpen

State variables that never change should be declared as `constant` to save gas. ```solidity=394 address payable private Wallet_Burn = payable(0x000000000000000000000000000000000000dEaD); ``` - `Wallet_Burn` should be declared `constant`. --- ```solidity=395 address payable private Wallet_zero = payable(0x0000000000000000000000000000000000000000); ``` - `Wallet_zero` should be declared `constant`. --- ```solidity=405 string private _name = "Layerium"; ``` - `_name` should be declared `constant`. --- ```solidity=406 string private _symbol = "LYUM"; ``` - `_symbol` should be declared `constant`. --- ```solidity=407 uint8 private _decimals = 18; ``` - `_decimals` should be declared `constant`. --- ```solidity=408 uint256 private _tTotal = 300000000 * 10**18; ``` - `_tTotal` should be declared `constant`. --- ```solidity=417 uint256 private maxPossibleFee = 24; ``` - `maxPossibleFee` should be declared `constant`.

Show 3 more
LowUnchecked Value of ERC-20 `transfer()`/`transferFOpen

The linked `transfer()`/`transferFrom()` invocations do not check the return value of the function call, which should yield `true` in the case of a proper ERC-20 implementation.

LowUnused State VariableOpen

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 `Wallet_zero` in `Layerium` is never used. ```solidity=395 address payable private Wallet_zero = payable(0x0000000000000000000000000000000000000000); ``` --- Variable `_tFeeTotal` in `Layerium` is never used. ```solidity=409 uint256 private _tFeeTotal; ``` --- Variable `_previousMaxWalletToken` in `Layerium` is never used. ```solidity=439 uint256 private _previousMaxWalletToken = _maxWalletToken; ``` --- Variable `_previousMaxTxAmount` in `Layerium` is never used. ```solidity=444 uint256 private _previousMaxTxAmount = _maxTxAmount; ```

LowUsage of `transfer`/`send` for Sending Native TokOpen

It is not recommended to use Solidity's `transfer()` and `send()` functions for transferring native tokens, since some contracts may not be able to receive the funds. Those functions forward only a fixed amount of gas (2300 specifically) and the receiving contracts may run out of gas before finishing the transfer. Also, EVM instructions' gas costs may increase in the future. Thus, some contracts that can receive now may stop working in the future due to the gas limitation. ```solidity=876 wallet.transfer(amount); ``` - `Layerium.sendToWallet()` function uses `transfer()`.

Contracts in scope1

AddressChainSource code
0x09cf7ca1b4ca6ab3855f23020e8e0e9e721cc03dChain: BNB ChainSource code: Verified source

Part of Layerium’s security record

Layerium
Audits1 audit by 1 firmLast audit Jul 2023, by CertiK.
Critical findingsNo open critical findingsIn 1 report.
Open Layerium’s security record

Security firm

CertiK

1,044 projects reviewed · 1,375 reports · latest May 2025

See CertiK’s audits

Something 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