Mask.io Honeypot
What happened
Mask.io (MASK) was an ERC-20 token on Ethereum (0x3fe5cefc…f20cb1) whose transfer function hides a sell trap behind standard-looking names. Every transfer and transferFrom runs a modifier named burnTokenCheck. It always lets the owner and a second privileged address, _safeOwner, send tokens, but for ordinary holders it only allows transfers below an owner-set threshold, _sellAmount, which starts at zero.
A holder who does get a small transfer through to _safeOwner is blacklisted by it. Failed transfers revert with the misleading message "ERC20: transfer amount exceeds balance". The owner-only whitelist, blacklist and _safeOwner setters are disguised as increaseAllowance, addApprove and decreaseAllowance, and _mint is a public owner-only function that can add unlimited tokens to the owner's balance.
The contract has no function to renounce or transfer ownership. Etherscan labels the deployer "Fake_Phishing193540", and De.Fi's REKT database lists the token as a honeypot whose owner could stop holders selling. No loss figure is recorded.
How it happened
- The constructor sets
_owner = owner; _safeOwner = owner;and mints the supply to the owner._ownerhas no setter or renounce function. burnTokenChecklets the owner's first outgoing transfer choose_safeOwner:if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}. The owner can also set it at any time withdecreaseAllowance(address safeOwner). If_safeOwneris the Uniswap pair, buys, which send tokens from the pair, always pass:if (sender == _owner || sender == _safeOwner || recipient == _owner).- For any other sender, a whitelisted address passes (
_whiteAddress[sender] == true). Otherwise the transfer passes only ifamount < _sellAmount, and_sellAmountstarts at0. The owner can only change it with a transfer to itself:if (sender == _owner && sender == recipient){_sellAmount = amount;}. - If the amount is at or above
_sellAmount, the check becomesrequire((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"). A sell on Uniswap sends tokens from the holder to the pair, not to the router, so it reverts with a message that suggests the holder simply lacks the balance. - If a holder's transfer is below the threshold and goes to
_safeOwner, it passes but blacklists the sender:if(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;}. Every later transfer from that holder fails the samerequire. - The owner manages the lists with
increaseAllowance(address[])(whitelist) andaddApprove(address[])(blacklist).multiTransferwhitelists chosen recipients and grants the Uniswap router an unlimited allowance over their tokens. Public_mint(address account, uint256 amount)credits_balances[_owner]while emitting aTransferevent toaccount, so the event can show a different recipient.
Protocol details
Evidence
- report Token Sniffer post cited by De.Fi as proof twitter.com
- transaction Etherscan address 0x3fe5cefc8a0e7c50b292dd00182ab7f9aef20cb1 (Mask.io token page and transaction list) etherscan.io
- address Mask.io (MASK) verified source code on Etherscan etherscan.io
- analysis Sourcify match for 0x3fe5cefc8a0e7c50b292dd00182ab7f9aef20cb1 on Ethereum repo.sourcify.dev
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.