CatgeSwap Rug Pull

Reported loss Not disclosed
BNB Chain
Rugpull

What happened

De.Fi's REKT database lists CatgeSwap (CATGES) as an exit scam / rug pull dated 2021-08-30 on Binance, with no loss amount. The verified contract at 0xf9eF4932…090E0A (deployed 2021-06-24 UTC by 0xDc8C749a…1557e4) is a MasterChef farm. In this code, withdraw and emergencyWithdraw reduce a staker's recorded balance but never transfer the staked LP tokens back, and the only function that moves pool LP tokens out sends every pool's balance to feeAddress.

Deposits therefore flow in with no exit for the staker. The code shows this design; no transaction opened for this record shows the sweep was called.

How it happened

  1. Stakers call deposit, which pulls LP tokens into the farm with pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount); and credits user.amount. The owner can set a pool deposit fee up to 100% (require(_depositFeeBP <= 10000, "add: invalid deposit fee basis points");).
  2. withdraw never returns tokens. After the lock check it only runs if(canWithdraw(_pid, msg.sender)){ if(_amount > 0){ user.amount = user.amount.sub(_amount); } and updates rewardDebt, then emits Withdraw. There is no safeTransfer to the caller in the function. The first call also sets user.withdrawLockedUp = block.timestamp.add(pool.withdrawInterval);, up to 14 days.
  3. emergencyWithdraw sets user.amount = 0; and emits EmergencyWithdraw(msg.sender, _pid, amount); with no transfer either, so calling it erases the staker's recorded stake and pays nothing.
  4. Reward harvesting is separate: deposit calls payOrLockupPendingCatge(_pid); even when _amount is 0, so reward tokens can still be paid while principal cannot be withdrawn.
  5. The one path that moves pool LP tokens out is migrate(): lpToken.safeTransfer(feeAddress, bal); for every pool, where bal is the farm's full balance of that LP token. It is called from setFeeAddress, which requires msg.sender == feeAddress and then require(feeAddress == _feeAddress, "setFeeAddress: SAME");, so the caller passes its own address and the whole sweep executes. feeAddress is a constructor argument that the source does not reveal.
  6. On the token side, each taxed transfer debits the sender in full: 6% by default, half to BURN_ADDRESS and half to the hard-coded FEE_ADDRESS 0x571AcF12…E94208, with maxTransferAmountRate = 50 (0.5% of supply) as the default per-transfer cap for non-excluded addresses. The operator can change the cap with updateMaxTransferAmountRate.
  7. The source does not show that setFeeAddress was called or that any holder tried to withdraw.

Protocol details

Classification Exchange (DEX)
Protocol Type Exit Scam/Rugpull
Affected asset / contract CATGES
Protocol links Website

Build your security review skills

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