AnimalHouse Finance Honeypot

Reported loss Not disclosed
BNB Chain
Honeypot

What happened

AnimalHouse Finance (AHOUSE, contract name AnimalHouse) is a BEP-20 token on BNB Smart Chain deployed from 0x2970BEE1…9F9D37 at contract address 0xd8e876ab…d6658b. Every transfer and transferFrom runs through a burnTokenCheck modifier that lets only the owner, a stored _safeOwner, whitelisted addresses and transfers to a hard-coded _unirouter address move tokens; any other holder's transfer reverts with a misleading "transfer amount exceeds balance" message. The owner alone can edit the whitelist and blacklist, change _safeOwner and mint more tokens.

De.Fi's REKT database lists the token as an Exit Scam/Honeypot in which the contract owner could disable the transfer function, restricting users from selling; it gives no loss figure and no transaction hashes.

How it happened

  1. The constructor takes the owner and an initial supply as arguments: _owner = owner; _safeOwner = owner; _mint(_owner, initialSupply*(10**18)); with _decimals = 18. _mint requires msg.sender == _owner, so the deployment succeeding means the deployer 0x2970BEE1…9F9D37 is the owner. No function assigns _owner again, so ownership cannot be moved or renounced.
  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 outgoing transfer makes its recipient the new _safeOwner.
  3. Privileged paths pass: if (sender == _owner || sender == _safeOwner || recipient == _owner) runs the transfer, and if (_whiteAddress[sender] == true){ _; } also runs it. Everyone else falls to the last branches.
  4. A blacklisted sender, and any other sender when amount >= _sellAmount, must satisfy require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"). _sellAmount starts at 0, so amount < _sellAmount is never true until the owner sets it. For these holders the only allowed destination is _unirouter, hard-coded as 0x05fF2B0D…608C7F (what that address is on chain was not checked). A sell that moves tokens from the holder to a trading pool is allowed only if the pool is that address; otherwise it reverts, even though the balance is sufficient. Transfers out of the pool to buyers likewise pass only if the pool is _safeOwner or whitelisted.
  5. The owner controls the lists. increaseAllowance(address[]) (despite its name) sets _whiteAddress = true and _blackAddress = false; addApprove(address[]) sets _blackAddress = true and _whiteAddress = false; decreaseAllowance(address safeOwner) overwrites _safeOwner; multiTransfer whitelists the first approvecount receivers and approves _unirouter for the maximum amount. All require msg.sender == _owner.
  6. The owner can also open a side door: a transfer from the owner to itself sets _sellAmount = amount. After that, a non-listed sender sending less than _sellAmount passes, and if the recipient is _safeOwner that sender is added to _blackAddress (if(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;}), so the same holder is restricted afterwards.
  7. _mint(address account, uint256 amount) is public but requires msg.sender == _owner, and it credits _balances[_owner] regardless of account, so the owner can create more supply.
  8. What happened on chain is not established here: no transaction showing a blocked sell, a list edit, a mint or liquidity removal was opened. Only De.Fi's REKT database listing states that selling was restricted.

Protocol details

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

Build your security review skills

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