Uniswap-V3 Honeypot

Reported loss Not disclosed
Ethereum
Honeypot

What happened

The Uniswap-V3 token (UNI-V3) is a standalone ERC-20 contract, named Uniswap in its source, that gates every transfer through a burnTokenCheck modifier. An ordinary holder can send tokens only to the hard-coded _unirouter address or to _owner; any other recipient reverts with "ERC20: transfer amount exceeds balance", although the holder's balance is not actually below the amount. The owner decides who is exempt through a whitelist, a blacklist and a _safeOwner address.

De.Fi's REKT database lists the token as an Exit Scam/Honeypot. The verified source shows these powers; it does not show which addresses the owner exempted or blocked.

How it happened

  1. Both transfer and transferFrom call _approveCheck(sender, recipient, amount), which carries the burnTokenCheck(sender,recipient,amount) modifier.
  2. The modifier lets a transfer pass unchecked when sender == _owner || sender == _safeOwner || recipient == _owner, or when _whiteAddress[sender] == true. While _owner == _safeOwner, a transfer sent by the owner also runs _safeOwner = recipient, so _safeOwner becomes the recipient of that first owner transfer. The code does not show which address that was.
  3. Every other sender is checked with require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"). Such a sender is never _safeOwner at that point (that case passed in step 2), so the transfer succeeds only if the recipient is the _unirouter address hard-coded in the contract (0x7a250d56…F2488D). A transfer to a liquidity pair or any other address reverts, and the revert leaves the holder's balance unchanged.
  4. The owner can change who falls on which side: increaseAllowance(address[]) whitelists and un-blacklists addresses, addApprove(address[]) blacklists and un-whitelists them, decreaseAllowance(address) sets _safeOwner, and multiTransfer sends tokens and whitelists the first approvecount receivers while approving _unirouter for the maximum amount on their behalf. A blacklisted sender faces the same require as an unlisted one.
  5. A small exception exists once _sellAmount is set, which happens when the owner sends tokens to itself (if (sender == _owner && sender == recipient){_sellAmount = amount;}). An unlisted holder sending less than _sellAmount is not reverted, but if the recipient is _safeOwner the holder is added to _blackAddress in the same call, so later transfers face the restriction again.
  6. The constructor makes eleven _mint calls: one of zero tokens and ten of initialSupply*(10**18), with the Transfer event naming eight distinct recipient addresses (0x209c4784…FC18EF three times). _mint is public, checks only msg.sender == _owner, and credits _balances[_owner] whatever account is passed, so every minted token lands with the owner while the events point at other addresses. The same function lets the owner increase supply later. No function changes _owner after the constructor.

Protocol details

Classification Token
Protocol Type DEX
Affected asset / contract UNI-V3
Protocol links Website @Uniswap

Security review history

Build your security review skills

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