TubbyCoin Honeypot
What happened
De.Fi's REKT database lists TubbyCoin (TUBBY) on Ethereum as an exit scam/honeypot and states that the contract owner could disable the transfer function, which restricted users in selling their tokens. In the verified source, every transfer runs through the burnTokenCheck modifier, which reverts for an ordinary holder unless the destination is the owner or the address stored in _unirouter. Because _sellAmount starts at 0, the only route for a holder to sell into a pool is to be whitelisted by the owner.
The owner also controls the whitelist, the blacklist, _safeOwner, _sellAmount, and an owner-only _mint. The code does not show which of these the owner used.
How it happened
- Each transfer,
transferandtransferFrom, calls_approveCheck, which is wrapped inburnTokenCheck(sender,recipient,amount).senderhere is the address the tokens leave, so the checks apply to the holder, not to the router calling on their behalf. - Privileged paths pass:
if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}(the first transfer out of the owner while_safeOwneris still the owner makes the recipient_safeOwner), andif (sender == _owner || sender == _safeOwner || recipient == _owner), so anyone can send tokens to the owner. A self-transfer by the owner also executes_sellAmount = amount;. - A sender with
_whiteAddress[sender] == trueis let through unconditionally. - A sender with
_blackAddress[sender] == truemust satisfyrequire((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"). Since_safeOwneralready passed in step 2, in practice a blacklisted holder can only send to_unirouter(0x7a250d56…F2488D). - A sender on neither list reaches
if (amount < _sellAmount)._sellAmountis initialised to0, soamount < 0is false and theelsebranch applies the samerequireas step 4: only transfers to_uniroutersucceed, and a transfer to any other recipient, such as a pool, reverts. Once the owner has set_sellAmount, a smaller transfer to_safeOwnersucceeds and runs_blackAddress[sender] = true; _whiteAddress[sender] = false;, so the sender is blacklisted from then on. - Owner-only functions control the lists:
multiTransferwhitelists the firstapprovecountreceivers and sets a maximum allowance for_unirouteron their balances,increaseAllowancewhitelists and un-blacklists a list of addresses,addApproveblacklists and un-whitelists them, anddecreaseAllowance(address safeOwner)replaces_safeOwner. _mintispublicwithrequire(msg.sender == _owner, ...)and addsamountto_balances[_owner], so the owner can increase the supply.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.