EA Sports Token Honeypot
What happened
EA Sports Token (EA) is an Ethereum token, contract 0x5ea43cbc…1962e8, whose every transfer and transferFrom runs through a burnTokenCheck modifier. Unless the sender is the owner, the _safeOwner address or a whitelisted address, the transfer reverts unless its recipient is the Uniswap V2 router address 0x7a250d56…F2488D or the owner, and the revert message imitates a balance error. A sell through the router moves tokens to the liquidity pair, so it reverts for ordinary holders.
The owner controls the whitelist, a blacklist, _safeOwner and a public _mint. De.Fi's REKT database lists EA as an exit scam/honeypot in which the contract owner could disable the transfer function, restricting users from selling their tokens. The sources opened here do not show which address _safeOwner held on-chain or any specific holder's failed sell.
How it happened
- The constructor sets
_owner = owner;and_safeOwner = owner;. Because the constructor's_mintcalls requiremsg.sender == _ownerand the deployment succeeded, the owner is the deployer, 0x862e7b37…051360. - The constructor calls
_mintten times. Each call credits_balances[_owner]whateveraccountis, while itsTransferevent namesaccount: five events name 0x862e7b37…051360 and five name 0xeaeAEAea…EAeAeA. All ten amounts are therefore held by the owner, and event logs do not match balances. - Every transfer path calls
_approveCheck, which carries theburnTokenCheckmodifier. Its first branch,if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}, moves_safeOwnerto the recipient of the owner's first outgoing transfer. If that transfer is the liquidity deposit (transferFrom(owner, pair, ...)), the pair becomes_safeOwner; the code alone does not show the on-chain value. The owner,_safeOwner, and any transfer to the owner then pass:if (sender == _owner || sender == _safeOwner || recipient == _owner). - A holder who is not whitelisted or blacklisted reaches the last branch.
_sellAmountdefaults to0, soamount < _sellAmountis false and the code runsrequire((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");. A router sell istransferFrom(holder, pair, amount): the sender is not_safeOwnerand the recipient is the pair, not_unirouter, so it reverts and the holder's balance is not debited. Wallet-to-wallet transfers revert the same way; only transfers to_unirouteror the owner pass. - The owner can change who passes:
increaseAllowance(despite its name) sets_whiteAddressto true and_blackAddressto false,addApprovedoes the reverse,decreaseAllowance(address safeOwner)replaces_safeOwner, andmultiTransferwhitelists its firstapprovecountrecipients and gives each a maximum allowance to_unirouter. All arerequire(msg.sender == _owner, "!owner")._mintispublicand owner-only, so the owner can mint more at any time. - A transfer from the owner to the owner (when
_owner != _safeOwner) runs_sellAmount = amount;. With_sellAmountabove zero, a non-listed holder's transfer of a smaller amount passes, and if the recipient is_safeOwnerthe holder is added by_blackAddress[sender] = true;, after which only transfers to_unirouterpass. Transfers of_sellAmountor more still revert.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.