CertiK reviewed 1MDC Coin (1MDC token - audit) on 20 Jun 2024: 18 findings, 0 critical; 6 acknowledged, 12 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 | 3 | 0 |
| Medium | 0 | 3 | 0 |
| Low | 12 | 0 | 0 |
In the contract `Ownable` the role `_owner` has authority over the functions shown in the diagram below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority and transfer ownership and renounce ownership.  In the contract `Token` the role `_owner` has authority over the functions shown in the diagram below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority and add addresses to exclude fees, add addresses to exclude fees VIP, set start time and add pairs.  In the contract `Token` the role `_tokenOwner` has authority over the functions shown in the diagram below. Any compromise to the `_tokenOwner` account may allow the hacker to take advantage of this authority and rescue token and cancel inviters. 
In the contract Token, the role `_tokenOwner` has the authority to withdraw all native coins and any amount of any tokens without sanity restriction. Any compromise to the `_tokenOwner` account may allow a hacker to take advantage of this authority and drain all assets from the contract. ```sol=1184 function rescueToken(address tokenAddress, uint256 tokens) public returns (bool success) { require(_tokenOwner == msg.sender); if(address(this).balance > 0){ payable(_tokenOwner).transfer(address(this).balance); } return IERC20(tokenAddress).transfer(msg.sender, tokens); } ```
All of the `1MDC` tokens are sent to the `tokenOwner` address. The `tokenOwner` address is a passed-in parameter through the `constructor()` function, and it could be an externally-owned account (EOA) address. This is a centralization risk because the deployer can distribute tokens without obtaining the consensus of the community. Any compromise to these addresses may allow a hacker to steal and sell tokens on the market, resulting in severe damage to the project.
The current smart contract computes total token circulating amount by subtracting the balances of specific excluded addresses from the `totalSupply`. The formula used is `totalTokenAmount = totalSupply - balanceOf(excluded address)`. Here the `uniswapV2Pair` address is the only excluded address. This approach is volatile and can lead to inaccuracies and manipulation of the total token circulating supply figure. If large balances of excluded addresses are deducted, it may not accurately reflect the supply available to the general public. Moreover, these addresses can alter the circulating supply by transferring tokens in or out, potentially affecting any functionality that relies on the circulating supply calculation. ```sol=1294 uint256 totalTokenAmount = super.totalSupply() - super.balanceOf(uniswapV2Pair); ```
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. An attacker can use a flashloan as the initial funds for an exploit to enlarge the profit or manipulate the token price in decentralized exchanges. If swapping/adding liquidity happens without price and slippage control and can be triggered on-chain by anyone, then an attacker can exploit the contract. The below functions call UniswapV2 interfaces without setting restrictions on slippage or minimum output amount, so transactions triggering the function are vulnerable to sandwich attacks, especially when the input amount is large. ```1237 function swapTokensForEth(uint256 tokenAmount) private { // generate the uniswap pair path of token -> weth address[] memory path = new address[](2); path[0] = address(this); path[1] = uniswapV2Router.WETH(); uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens( tokenAmount, 0, // accept any amount of ETH path, address(this), block.timestamp ); } ``` Function `Token.swapTokensForEth` invokes `uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens` without setting restrictions on slippage or minimum output amount, which may be triggered by anyone. --- ```1251 function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private { uniswapV2Router.addLiquidityETH{value: ethAmount}( address(this), tokenAmount, 0, 0, _destroyAddress, block.timestamp ); } ``` Function `Token.addLiquidity` invokes `uniswapV2Router.addLiquidityETH` without setting restrictions on slippage or minimum output amount, which may be triggered by anyone. --- ```1262 function swapPoolTokensForUsdt(uint256 tokenAmount) private { address[] memory path = new address[](3); path[0] = address(this); path[1] = _baseToken; path[2] = address(USDT); uniswapV2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens( tokenAmount, 0, path, address(this), block.timestamp ); } ``` Function `Token.swapPoolTokensForUsdt` invokes `uniswapV2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens` without setting restrictions on slippage or minimum output amount, which may be triggered by anyone.
In the `_transfer()` function of the contract `Token`, if the `to` address is pair, after the start time check and the distribution of the USDT reward, it will call the `destroyPoolToken()` function. Here the `destroyPoolToken()` function calls `super._transfer(uniswapV2Pair, _destroyAddress, ...)` to burn a certain amount of tokens from the Uniswap pool to the pre-defined `_destroyAddress`. Then the function calls `IUniswapV2Pair(uniswapV2Pair).sync()` to update the Uniswap pool's reserves, since the asset balance changes does not go through the usual `swap()`, `addLiquidity()`, or `removeLiquidity()` UniswapV2 functions ```sol=1079 function destroyPoolToken() private { uint256 nowHour = block.timestamp.div(daySecond); if(nowHour > lastHour){ lastHour = nowHour; uint256 haveAmount = super.balanceOf(uniswapV2Pair); if(haveAmount > 10**22){ super._transfer(uniswapV2Pair, _destroyAddress, haveAmount.div(100).mul(12).div(24)); IUniswapV2Pair(uniswapV2Pair).sync(); } } } ``` Now if we look back to the `_transfer()` function, after the `else if` statement finished, the function will call the `super._transfer()` method to increase the 1MDC token amount by direct token transferring without calling the `sync()` function. ```sol=1131 }else if(_isPairs[to]){ require(startTime < block.timestamp,"startTime"); _splitUsdt2BuyTokenHolder(); destroyPoolToken(); super._transfer(from, address(this), amount.div(100).mul(3)); amount = amount.div(100).mul(97); //_transfer2otherAddress(amount.div(10000)); }else{ } } super._transfer(from, to, amount); ```
| Address | Chain | Source code |
|---|---|---|
| 0xf43c…c6730xf43c9b40c9361b301019c98fb535affb3ec6c673 | 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