AstroDoge Honeypot

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

  1. The constructor sets _owner = owner; and _safeOwner = owner; and mints the initial supply to the owner. _sellAmount starts at 0 and _unirouter is fixed at 0x05fF2B0D…608C7F.
  2. transfer and transferFrom both call _approveCheck, which carries the burnTokenCheck(sender,recipient,amount) modifier. Its first branch is if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}: while _safeOwner still equals the owner, the owner's first send stores its recipient as _safeOwner. If that send is the liquidity add, the pair becomes _safeOwner.
  3. 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.
  4. 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 if amount < _sellAmount; otherwise the same require applies.
  5. With _sellAmount at its default of 0, amount < _sellAmount is 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 runs if(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;}, adding the seller to the blacklist so later transfers are restricted to _unirouter.
  6. 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, and multiTransfer whitelists the first approvecount receivers and approves _unirouter for the maximum amount on their behalf.
  7. _mint is public but requires msg.sender == _owner; it runs _totalSupply = _totalSupply.add(amount); _balances[_owner] = _balances[_owner].add(amount);, crediting the owner whatever account is. The owner passes the second branch for every send, so the code lets minted tokens be sold to the pair. _owner is assigned only in the constructor; no function transfers or renounces it.

Protocol details

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

Build your security review skills

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