AstroDoge Honeypot
What happened
AstroDoge (ADG, BNB Chain, contract 0x149f3615…673937) sends every transfer and transferFrom through a burnTokenCheck modifier that treats the owner, a stored _safeOwner address, a whitelist and a blacklist differently. With the default _sellAmount of 0, an ordinary holder's transfer reverts unless the recipient is the hard-coded _unirouter address, so a sale into a trading pair reverts. Whether buyers can still receive tokens from the pair depends on whether the pair was stored as _safeOwner (or whitelisted); otherwise a transfer out of the pair hits the same check.
The owner can also whitelist or blacklist addresses, change _safeOwner, and call a public _mint that credits the owner's balance. De.Fi's REKT database lists the token as an Exit Scam/Honeypot dated 2021-06-16 and says the owner could disable the transfer function, which restricted users in selling their tokens; the listing gives no loss amount.
How it happened
- The constructor sets
_owner = owner;and_safeOwner = owner;and mints the initial supply to the owner._sellAmountstarts at 0 and_unirouteris fixed at 0x05fF2B0D…608C7F. transferandtransferFromboth call_approveCheck, which carries theburnTokenCheck(sender,recipient,amount)modifier. Its first branch isif (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}: while_safeOwnerstill equals the owner, the owner's first send stores its recipient as_safeOwner. If that send is the liquidity add, the pair becomes_safeOwner.- Next branch:
if (sender == _owner || sender == _safeOwner || recipient == _owner){ if (sender == _owner && sender == recipient){_sellAmount = amount;}_;}. The owner,_safeOwner(so buys from the pair, if the pair is the stored_safeOwner) and anyone sending to the owner pass unchecked; a transfer from the owner to itself sets_sellAmount. - For every other sender: a whitelisted sender passes. A blacklisted sender hits
require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"), which in practice means the recipient must be_unirouter. An address on neither list passes only ifamount < _sellAmount; otherwise the samerequireapplies. - With
_sellAmountat its default of 0,amount < _sellAmountis never true, so a holder on neither list can send only to_unirouter. A sale to the pair, or any transfer to another wallet, reverts with "ERC20: transfer amount exceeds balance". If the owner raises_sellAmount, a sale below it succeeds but runsif(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;}, adding the seller to the blacklist so later transfers are restricted to_unirouter. - The list setters carry ERC-20 style names but are owner-only (
require(msg.sender == _owner, "!owner")):increaseAllowance(address[])whitelists and un-blacklists,addApprove(address[])blacklists and un-whitelists,decreaseAllowance(address safeOwner)replaces_safeOwner, andmultiTransferwhitelists the firstapprovecountreceivers and approves_unirouterfor the maximum amount on their behalf. _mintispublicbut requiresmsg.sender == _owner; it runs_totalSupply = _totalSupply.add(amount); _balances[_owner] = _balances[_owner].add(amount);, crediting the owner whateveraccountis. The owner passes the second branch for every send, so the code lets minted tokens be sold to the pair._owneris assigned only in the constructor; no function transfers or renounces it.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.