DeFi Wonderland 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:
- The constructor sets
_owner = owner;and_safeOwner = owner;and_sellAmountstarts at0. transferandtransferFromboth call_approveCheck, which is wrapped in theburnTokenCheckmodifier. While_owner == _safeOwner, the first branchif (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}lets any owner-sent transfer through and moves_safeOwnerto its recipient. The code does not show who that recipient is.- 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_sellAmountwith a self-transfer. That line is reachable only once_owner != _safeOwner, because while they are equal the first branch fires instead. - Any other sender whose
_whiteAddress[sender]is true passes to any recipient. - A sender whose
_blackAddress[sender]is true reachesrequire((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"). Because_safeOwnersenders were already handled in step 3, this passes only when the recipient is_unirouter(0x7a250d56…F2488D). - A sender on neither list is tested with
if (amount < _sellAmount). If it is true, the transfer passes to any recipient, andif(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;}blacklists the sender when the recipient is_safeOwner. Otherwise the samerequire((sender == _safeOwner)||(recipient == _unirouter), ...)applies. While_sellAmountis0,amount < _sellAmountis never true, so such a sender can transfer only to_unirouteror to the owner. - The owner controls the lists:
increaseAllowance(address[] receivers)sets_whiteAddress[...] = trueand_blackAddress[...] = false,addApprove(address[] receivers)does the reverse,decreaseAllowance(address safeOwner)replaces_safeOwner, andmultiTransferwhitelists the firstapprovecountrecipients. All requiremsg.sender == _owner. - The constructor calls
_mint(0x7A1057E6e9093DA9C1D4C1D049609B6889fC4c67, initialSupply*(10**18))25 times and_mint(0x2D407dDb06311396fE14D4b49da5F0471447d45C, initialSupply*(10**18))25 times, but_mintcredits_balances[_owner] = _balances[_owner].add(amount)and only emitsTransfer(address(0), account, amount)for the named account. The mint events therefore point at two addresses while the balance is recorded under_owner._mintis 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
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.