HungryWorldBSC Honeypot
What happened
HungryWorldBSC (HGW) routes every transfer and transferFrom through a modifier called burnTokenCheck. For a holder who is not on the owner's whitelist, the modifier only lets tokens go to the hard-coded _unirouter address or to _owner; a transfer to any other recipient reverts with the message "ERC20: transfer amount exceeds balance" even when the balance is sufficient. The owner can whitelist or blacklist addresses, move the privileged _safeOwner address and mint to itself.
De.Fi's REKT database lists HGW on BNB Smart Chain as an Exit Scam/Honeypot and says the contract owner could disable the transfer function, which restricted users in selling their tokens. The code shows what the owner can do; no transaction was opened to show which addresses were listed.
How it happened
- The constructor sets
_owner = owner;and_safeOwner = owner;and mintsinitialSupply*(10**18)to_owner; the on-chain supply read is 5,000,000,000 tokens._owneris assigned only there, so the contract has no function to transfer or renounce it. transferandtransferFromboth call_approveCheck, which is declaredinternal burnTokenCheck(sender,recipient,amount). The check runs before any balance is debited, so a blocked transfer reverts without moving tokens.- The first transfer sent by the owner re-points the privileged address:
if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}. After that, transfers where the sender is_owneror_safeOwner, or where the recipient is_owner, pass:if (sender == _owner || sender == _safeOwner || recipient == _owner){. - Every other sender is checked in this order. A sender with
_whiteAddress[sender] == truepasses. A sender with_blackAddress[sender] == truemust satisfyrequire((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"), which for a non-privileged sender means the recipient is_unirouter. - A sender on neither list reaches
if (amount < _sellAmount){._sellAmountstarts at 0, so that test is false for every amount and the sender falls to the samerequire: only a transfer to_uniroutersucceeds. A sale that delivers tokens to any other address, for example a liquidity pair, reverts. A buy sends tokens out of the pair, so it passes only if the pair is whitelisted or is_safeOwner; the source does not show either. _sellAmountis set when the owner sends tokens to itself:if (sender == _owner && sender == recipient){_sellAmount = amount;}. Once it is set, a non-listed holder can move amounts below it, and doing so to_safeOwnerflags the sender:if(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;}.- Owner-only functions manage the lists.
increaseAllowance(address[] memory receivers)sets_whiteAddressto true and_blackAddressto false,addApprovedoes the opposite,decreaseAllowance(address safeOwner)replaces_safeOwner, andmultiTransferwhitelists its firstapprovecountreceivers and grants each a maximum allowance to_unirouter. The names do not describe what these functions do. _mint(address account, uint256 amount)ispublicbut requiresmsg.sender == _owner, and it adds the amount to_balances[_owner]. The owner can therefore increase its own balance at any time; the source alone does not show that it did.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.