1MILNFT Honeypot
What happened
De.Fi's REKT database lists 1MILNFT (1MIL) as an exit scam/honeypot on Ethereum, saying the owner could disable transfers and so stop holders from selling. The verified source does not contain a transfer-off switch, but it does contain a sender-based transfer filter. In burnTokenCheck, a holder who is not whitelisted by the owner can only send tokens to the hard-coded _unirouter address or to _owner; every other transfer reverts with a balance-style error message.
The owner alone controls the whitelist, the blacklist and the _safeOwner address that the filter keys on, and can also call _mint.
How it happened
- The constructor sets
_owner = msg.sender;and_safeOwner = msg.sender;, then mints 1,000,000 tokens (_mint(_owner, 1*1e6*(10**18));) to the deployer._ownerhas no setter and the contract has no ownership-transfer or renounce function. - Both
transferandtransferFromcall_approveCheck, which runs theburnTokenCheckmodifier on the token holder (sender) and therecipient. - The modifier's first branch is
if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}. While_owner == _safeOwner, every transfer sent by the owner, including one to itself, takes this branch: it passes and re-points_safeOwnerto the recipient, and it does not touch_sellAmount. Once_safeOwnerdiffers from_owner(after an owner-sent transfer to another address, or afterdecreaseAllowance), the second branchif (sender == _owner || sender == _safeOwner || recipient == _owner)lets the owner and_safeOwnerthrough, and only there does an owner transfer to itself set_sellAmount = amount. - Any other sender who is on the owner's whitelist (
_whiteAddress[sender] == true) passes. Everyone else lands in the final branch._sellAmountstarts at0, soamount < _sellAmountis never true until the owner sets it, and the code runsrequire((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");. An ordinary holder can therefore only send to_unirouter(or to_owner); a transfer to any other address, such as a pool, reverts. - The owner controls who is exempt.
increaseAllowance(address[] receivers)whitelists addresses and un-blacklists them;addApprove(address[] receivers)blacklists addresses;decreaseAllowance(address safeOwner)replaces_safeOwner;multiTransfersends tokens and whitelists the firstapprovecountreceivers. A blacklisted sender is held to the same router-only rule as an ordinary holder. - After
_safeOwnerhas moved off_ownerand the owner has made a self-transfer to set_sellAmount, ordinary non-whitelisted holders can move amounts below it to any address, but a transfer of such an amount to_safeOwnerflags the sender as blacklisted:if(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;}. - Separately,
_mint(address account, uint256 amount)ispublicand guarded only byrequire(msg.sender == _owner, ...); it adds to_balances[_owner]whateveraccountis passed, so the owner can mint additional supply to itself at any time.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.