Diamond Hands Inu Honeypot
What happened
Diamond Hands Inu (DHINU) is an Ethereum token whose transfer path runs through a burnTokenCheck modifier. The owner, a _safeOwner address, owner-whitelisted addresses and transfers into the owner pass freely. For other holders, the default path is a require that lets a transfer through only when the recipient is the Uniswap V2 router address hard-coded as _unirouter; otherwise it reverts with "ERC20: transfer amount exceeds balance".
A sell through a trading pair sends tokens to the pair, not to the router, so by default it reverts for an ordinary holder. The owner can also change this default: a self-transfer by the owner sets _sellAmount, and after that a transfer from an ordinary holder with a smaller amount can pass without going to the router. The owner can also whitelist or blacklist addresses and can mint to itself.
De.Fi's REKT database lists the token as an Exit Scam/Honeypot and says the contract owner could restrict users from selling.
How it happened
- The constructor sets
_ownerand_safeOwnerto the deployer and calls_mint(_owner, initialSupply*(10**18)), which creates 1,000,000,000 tokens (18 decimals) for the owner. - Every
transferandtransferFromgoes through_approveCheck, which carries the modifierburnTokenCheck(sender,recipient,amount). - In the modifier, a sender that is
_ownerwhile_owner == _safeOwnerpasses and_safeOwneris set to the recipient. After that, a transfer passes ifsender == _owner || sender == _safeOwner || recipient == _owner. Inside that branch, ifsender == _owner && sender == recipient, the transfer also sets_sellAmount = amount. So once_safeOwnerhas moved off the owner, the owner can set_sellAmountwith a self-transfer. - For any other sender, a whitelisted sender (
_whiteAddress[sender] == true) can transfer to any recipient. A blacklisted sender passes only ifsender == _safeOwnerorrecipient == _unirouter. - A sender that is neither whitelisted nor blacklisted is checked against
_sellAmount, which starts at 0. While_sellAmountis 0,amount < _sellAmountis false, so the code reachesrequire((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");and the transfer reverts unless the recipient is the router (the owner as recipient was already allowed in step 3). If the owner has set_sellAmountthrough a self-transfer, a transfer withamount < _sellAmountcan pass with no router requirement, and the sender is blacklisted and removed from the whitelist only ifrecipient == _safeOwner. A transfer withamount >= _sellAmountstill faces the routerrequire. - A sell through a pair is a
transferFrom(holder, pair, amount). The recipient is the pair, so by default, for a holder who is not whitelisted, it reverts and the balance is not debited. Whether a given sell can pass depends on whether the owner has set_sellAmountand whetheramountis below it. Buys, where the pair is the sender, can only pass if the pair is_safeOwneror whitelisted, or if the_sellAmountpath applies; the source does not show which address the pair was or what_sellAmountwas set to. - The owner controls the lists:
increaseAllowancewhitelists addresses,addApproveblacklists them (and removes them from the whitelist),decreaseAllowance(address safeOwner)sets_safeOwner, andmultiTransferwhitelists the firstapprovecountreceivers. These function names do not match what they do. _mintispublicwithrequire(msg.sender == _owner, ...)and adds to_balances[_owner], so the owner can create new supply for itself at any time. The source alone does not show whether this was done.- What happened on-chain (sell attempts, liquidity removal, losses, any
_sellAmountvalue) was not established by the opened sources. De.Fi's REKT database lists the token as a honeypot.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.