BabyDAO Rug Pull

Reported loss $8K
Rugpull

What happened

BabyDAO (BabyDAO) is a fixed-supply BNB Smart Chain token whose _internalTransfer lets a small set of "synced" addresses decide who can trade. The deployer is synced at construction, and any synced address can call claim to mark a holder as claimed; a claimed holder's transfers into the PancakeSwap pair revert with "Claimed", so that wallet can be stopped from selling while buys still work. The owner can also switch trading off for all non-synced addresses with openTrading(false), and the contract has no mint or liquidity lock.

The deployer created the token in tx 0x3af2f636…650e88 (10,000,000,000 tokens to the deployer 0xe153c4b6…29c78a). De.Fi's REKT database lists the token as a rug pull of about $7.6k: a PancakeSwap pair was created in tx 0x8ad196e5…8f183b with 10 WBNB and 100M BabyDAO; about 14 days later 300 BNB reached the deployer (tx 0x166c1ced…231429) and 290 BNB went into the pool (tx 0x67e83954…ccec3c); after the price rose the deployer removed liquidity for a listed profit of 32.25 WBNB, and the funds were sent on to 0xd759da59…eb7f0a (tx 0x40a34d6f…a31ea3) and laundered through Tornado.cash, per the listing.

How it happened

  1. What the code allows: the constructor gives the whole supply to the deployer and marks the deployer as synced: _tokenBalances[_msgSender()] = _tokenTotalSupply; and _synced[owner()] = true;. _tokenTotalSupply = 1000000000000 * 1e7 is 1e19 base units, or 10,000,000,000 tokens at 9 decimals.
  2. Trading starts closed. _internalTransfer only runs its checks when both parties are unsynced: if (!_synced[sender] && !_synced[recipient]) { require(_isTradingOpen);. _isTradingOpen defaults to false and is set only by openTrading(bool value) public onlyOwner, which can close trading again at any time. Synced addresses skip every check.
  3. Selling can be blocked per wallet. _isSwapEnabled is set to true in the constructor and has no setter, so unsynced transfers must touch the pair (require(sender == uniswapV2Pair || recipient == uniswapV2Pair);), and a transfer into the pair runs require(!_claimed[sender], "Claimed"); and require(!_claimed[_msgSender()], "Claimed");. A router sale calls transferFrom(holder, pair, amount), so sender is the holder and a claimed holder's sale reverts. A buy has sender == uniswapV2Pair, never reaches the _claimed check and goes through.
  4. The flag is set by synced addresses: function claim(address[] calldata wallet, bool value) external onlySynced writes _claimed[wallet[i]] = value;, and sync lets any synced address add further synced addresses. These functions are gated by _synced, not by ownership, so renounceOwnership would not remove the deployer's synced status.
  5. What happened, per De.Fi's REKT database: the deployer paired the token with BNB on PancakeSwap, added more liquidity, then removed liquidity at a higher price for a listed 32.25 WBNB profit and moved the proceeds to a second address for Tornado.cash. No opened transaction shows whether claim, sync or openTrading(false) was called on any holder.

Protocol details

Classification Other
Protocol Type Exit Scam/Rugpull
Affected asset / contract BabyDAO

Build your security review skills

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