SafeCash Honeypot
What happened
SafeCash (SAFECASH) is a BNB Chain token whose transfer logic runs through a burnTokenCheck modifier controlled by its owner. Unless the sender is the owner, the address stored as _safeOwner, or whitelisted by the owner, a transfer is allowed only if the recipient is the hard-coded _unirouter address or the owner. While _sellAmount is 0, which is its initial value, a sell into a pair can therefore revert with "ERC20: transfer amount exceeds balance" even when the holder has the tokens.
Once the owner has set a positive _sellAmount, a sender moving less than that amount passes instead. De.Fi's REKT database lists the token as an exit scam/honeypot, saying the contract owner could restrict users from selling.
How it happened
- The constructor takes the owner as an argument and sets both
_ownerand_safeOwnerto it, then mints the initial supply:_mint(_owner, initialSupply*(10**18));, credited to_balances[_owner]. - Both
transferandtransferFromcall_approveCheck, which is wrapped inburnTokenCheck(sender,recipient,amount). While_owner == _safeOwner, a transfer from the owner passes and moves_safeOwnerto its recipient:if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}. - After that, transfers pass freely if the sender is
_owneror_safeOwner, or if the recipient is_owner. A whitelisted sender also passes. A blacklisted sender passes only ifsender == _safeOwneror the recipient is_unirouter. - Every other sender falls to
if (amount < _sellAmount){...}else{require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");_;}._sellAmountstarts at 0, soamount < _sellAmountis false and, while it stays 0, such a sender can only send to_unirouter(or to_ownerunder step 3). A sale that transfers tokens to a pair address can then revert unless that sender is whitelisted. If_sellAmountbecomes positive, transfers below it pass. - The owner controls the lists and thresholds:
increaseAllowance(address[])whitelists addresses,addApprove(address[])blacklists them, anddecreaseAllowance(address)sets_safeOwner. The owner can also set_sellAmount, but only after_safeOwnerhas moved off the owner: the lineif (sender == _owner && sender == recipient){_sellAmount = amount;}is reached only when_owner != _safeOwner, because while_owner == _safeOwnerthe first branch runs instead and a self-transfer just sets_safeOwner = recipient. Once_sellAmountis above 0, a non-privileged sender sending less than it passes, and if the recipient is_safeOwnerthe sender is added to_blackAddress. _mintispublicand only checksmsg.sender == _owner, so the code lets the owner mint further supply into the owner's own balance. There is no function that renounces or transfers_owner.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.