Coin Merge Rug Pull

Reported loss $76K
Rugpull

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**

  1. The owner can flag any address with function setBot(address _address, bool toggle) external onlyOwner { isBot[_address] = toggle;. In takeFee, a sell (sender != pair) checks bot = isBot[sender] and a buy checks bot = isBot[recipient]; the flagged side pays feeAmount = 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 full amount (_balances[sender] = _balances[sender].sub(amount, "Insufficient Balance");), so the flagged holder receives or delivers only the remaining 1%.
  2. The same 99% branch runs for everyone who is not fee-exempt when launchedAt + initialBlockLimit >= block.number. function setInitialBlockLimit(uint256 blocks) external onlyOwner only requires blocks > 0, so a very large value keeps the 99% fee active long after launch. The ordinary fee is capped by require(totalFee < feeDenominator/2); in setFees, but this path is not.
  3. Sells by non-exempt addresses are checked in checkTxLimit with require(amount <= _maxTxAmountSell || isTxLimitExempt[sender], "Sell TX Limit Exceeded"). function setSellTxLimit(uint256 amount) external onlyOwner has 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;).
  4. There is no renounceOwnership; transferOwnership only reassigns the owner, so these switches stay available to whoever holds ownership.

**What happened**

  1. On 2023-02-08 at 00:58:23 UTC, 0xE990d297…76AB0d sent removeLiquidityWithPermit to 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 _transferFrom and therefore through checkTxLimit and shouldTakeFee. 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

Classification Token
Protocol Type Exit Scam/Rugpull
Affected asset / contract CMERGE

Build your security review skills

Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.