Coin Merge Rug Pull
What happened
Coin Merge (CMERGE) is a reflection-style ERC20 whose owner holds several switches that can turn the token into a trap for non-exempt holders: setBot can mark an address so that 99% of every buy or sell it makes is sent to the dead address, setInitialBlockLimit accepts any block count and applies the same 99% fee to every non-exempt transfer while the count lasts, and setSellTxLimit can set the sell cap so low that sells revert. On 2023-02-08 at 00:58:23 UTC, address 0xE990d297…76AB0d called removeLiquidityWithPermit on Uniswap V2 Router 2 and received 45.3709 WETH and about 283.95 million CMERGE from the CMERGE/WETH pool (transaction 0x969d2e42…592886). The token's current getOwner() read is the same address.
De.Fi's REKT database lists the incident as an exit scam/rugpull of 75,887 USD and lists the deployer as 0x93Ec3124…99db6f. No transaction opened shows the owner calling any of the fee or limit switches.
How it happened
**What the code allows**
- The owner can flag any address with
function setBot(address _address, bool toggle) external onlyOwner { isBot[_address] = toggle;. IntakeFee, a sell (sender != pair) checksbot = isBot[sender]and a buy checksbot = isBot[recipient]; the flagged side paysfeeAmount = amount.mul(feeDenominator.sub(1)).div(feeDenominator);, which is 99% when the denominator is 100, and that amount is credited to_balances[DEAD]. The sender's balance has already been reduced by the fullamount(_balances[sender] = _balances[sender].sub(amount, "Insufficient Balance");), so the flagged holder receives or delivers only the remaining 1%. - The same 99% branch runs for everyone who is not fee-exempt when
launchedAt + initialBlockLimit >= block.number.function setInitialBlockLimit(uint256 blocks) external onlyOwneronly requiresblocks > 0, so a very large value keeps the 99% fee active long after launch. The ordinary fee is capped byrequire(totalFee < feeDenominator/2);insetFees, but this path is not. - Sells by non-exempt addresses are checked in
checkTxLimitwithrequire(amount <= _maxTxAmountSell || isTxLimitExempt[sender], "Sell TX Limit Exceeded").function setSellTxLimit(uint256 amount) external onlyOwnerhas no lower bound, so setting it to 0 would make every non-exempt sell revert while the owner and team wallet stay exempt (isTxLimitExempt[_owner] = true;). - There is no
renounceOwnership;transferOwnershiponly reassigns the owner, so these switches stay available to whoever holds ownership.
**What happened**
- On 2023-02-08 at 00:58:23 UTC, 0xE990d297…76AB0d sent
removeLiquidityWithPermitto Uniswap V2 Router 2 and received 45.3709 WETH and about 283.95 million CMERGE, burning 3.38 LP tokens (transaction 0x969d2e42…592886). The CMERGE leg of that removal is a transfer from the pair to the recipient, which goes through the token's_transferFromand therefore throughcheckTxLimitandshouldTakeFee. The transaction completed and delivered about 283.95 million CMERGE, so the buy-side limit did not block it; the opened sources do not show whether that was because the limit was left at its default or because the recipient was exempt, or whether a fee was taken on the amount.
Protocol details
Evidence
- transaction Etherscan transaction 0x969d2e42ab71a2e483570c577dc758b294e638d8a057eddee4ede4dbb0592886 etherscan.io
- address Coin Merge (CMERGE) contract code (Etherscan) etherscan.io
- address etherscan.io address 0x93Ec…db6f etherscan.io
- address etherscan.io address 0xE990…AB0d etherscan.io
- analysis Coin Merge on Sourcify repo.sourcify.dev
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.