DeFi Wonderland Honeypot

Reported loss Not disclosed
Ethereum
Honeypot

What happened

DeFi Wonderland (WNDR) is an Ethereum token whose transfer and transferFrom calls pass through a burnTokenCheck modifier. A holder who is not the owner, not _safeOwner and not on the owner's whitelist can move tokens only to the hard-coded router address (_unirouter) or to the owner, unless the owner has set a _sellAmount threshold, in which case smaller transfers pass to any recipient. Any other transfer reverts with "ERC20: transfer amount exceeds balance".

Only the owner can whitelist or blacklist addresses, replace _safeOwner, or set _sellAmount. De.Fi's REKT database lists the token as an Exit Scam/Honeypot, describing a contract whose owner could disable the transfer function, which restricted users in selling their tokens.

How it happened

What the code allows:

  1. The constructor sets _owner = owner; and _safeOwner = owner; and _sellAmount starts at 0.
  2. transfer and transferFrom both call _approveCheck, which is wrapped in the burnTokenCheck modifier. While _owner == _safeOwner, the first branch if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;} lets any owner-sent transfer through and moves _safeOwner to its recipient. The code does not show who that recipient is.
  3. The second branch if (sender == _owner || sender == _safeOwner || recipient == _owner){ lets the owner, the current _safeOwner, and anyone sending to the owner transfer without further checks. Inside it, if (sender == _owner && sender == recipient){_sellAmount = amount;} lets the owner set _sellAmount with a self-transfer. That line is reachable only once _owner != _safeOwner, because while they are equal the first branch fires instead.
  4. Any other sender whose _whiteAddress[sender] is true passes to any recipient.
  5. A sender whose _blackAddress[sender] is true reaches require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"). Because _safeOwner senders were already handled in step 3, this passes only when the recipient is _unirouter (0x7a250d56…F2488D).
  6. A sender on neither list is tested with if (amount < _sellAmount). If it is true, the transfer passes to any recipient, and if(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;} blacklists the sender when the recipient is _safeOwner. Otherwise the same require((sender == _safeOwner)||(recipient == _unirouter), ...) applies. While _sellAmount is 0, amount < _sellAmount is never true, so such a sender can transfer only to _unirouter or to the owner.
  7. The owner controls the lists: increaseAllowance(address[] receivers) sets _whiteAddress[...] = true and _blackAddress[...] = false, addApprove(address[] receivers) does the reverse, decreaseAllowance(address safeOwner) replaces _safeOwner, and multiTransfer whitelists the first approvecount recipients. All require msg.sender == _owner.
  8. The constructor calls _mint(0x7A1057E6e9093DA9C1D4C1D049609B6889fC4c67, initialSupply*(10**18)) 25 times and _mint(0x2D407dDb06311396fE14D4b49da5F0471447d45C, initialSupply*(10**18)) 25 times, but _mint credits _balances[_owner] = _balances[_owner].add(amount) and only emits Transfer(address(0), account, amount) for the named account. The mint events therefore point at two addresses while the balance is recorded under _owner. _mint is also public and owner-only with no cap.

This describes what the code allows. The supplied source does not show which owner functions were called on this deployment.

Protocol details

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

Build your security review skills

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