Uniswap-V3 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
- Both
transferandtransferFromcall_approveCheck(sender, recipient, amount), which carries theburnTokenCheck(sender,recipient,amount)modifier. - 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_safeOwnerbecomes the recipient of that first owner transfer. The code does not show which address that was. - Every other sender is checked with
require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"). Such a sender is never_safeOwnerat that point (that case passed in step 2), so the transfer succeeds only if the recipient is the_unirouteraddress 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. - 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, andmultiTransfersends tokens and whitelists the firstapprovecountreceivers while approving_unirouterfor the maximum amount on their behalf. A blacklisted sender faces the samerequireas an unlisted one. - A small exception exists once
_sellAmountis set, which happens when the owner sends tokens to itself (if (sender == _owner && sender == recipient){_sellAmount = amount;}). An unlisted holder sending less than_sellAmountis not reverted, but if the recipient is_safeOwnerthe holder is added to_blackAddressin the same call, so later transfers face the restriction again. - The constructor makes eleven
_mintcalls: one of zero tokens and ten ofinitialSupply*(10**18), with theTransferevent naming eight distinct recipient addresses (0x209c4784…FC18EF three times)._mintispublic, checks onlymsg.sender == _owner, and credits_balances[_owner]whateveraccountis 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_ownerafter the constructor.
Protocol details
Security review history
- ConsenSys Diligence View report
- ABDK Consulting View report
- Trail of Bits View report
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.