Mask.io Honeypot

Reported loss Not disclosed
Ethereum
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

  1. The constructor sets _owner = owner; _safeOwner = owner; and mints the supply to the owner. _owner has no setter or renounce function.
  2. burnTokenCheck lets the owner's first outgoing transfer choose _safeOwner: if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}. The owner can also set it at any time with decreaseAllowance(address safeOwner). If _safeOwner is the Uniswap pair, buys, which send tokens from the pair, always pass: if (sender == _owner || sender == _safeOwner || recipient == _owner).
  3. For any other sender, a whitelisted address passes (_whiteAddress[sender] == true). Otherwise the transfer passes only if amount < _sellAmount, and _sellAmount starts at 0. The owner can only change it with a transfer to itself: if (sender == _owner && sender == recipient){_sellAmount = amount;}.
  4. If the amount is at or above _sellAmount, the check becomes require((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.
  5. 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 same require.
  6. The owner manages the lists with increaseAllowance(address[]) (whitelist) and addApprove(address[]) (blacklist). multiTransfer whitelists chosen recipients and grants the Uniswap router an unlimited allowance over their tokens. Public _mint(address account, uint256 amount) credits _balances[_owner] while emitting a Transfer event to account, so the event can show a different recipient.

Protocol details

Classification Token
Protocol Type Exit Scam/Honeypot
Affected asset / contract MASK

Build your security review skills

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