SafeCompound Honeypot
What happened
De.Fi's REKT database lists SafeCompound (SAFECOMPOUND) on BNB Smart Chain as an Exit Scam/Honeypot, noting that the contract owner could disable the transfer function and so stop users selling. The verified source at 0x023e0c72…a39e87 does not switch every transfer off. Instead, each transfer and transferFrom runs through a burnTokenCheck modifier under which, by default, a holder's tokens can only be sent to one hard-coded address, _unirouter; a transfer to any other destination reverts with ERC20: transfer amount exceeds balance even when the balance is sufficient.
Owner-only functions can whitelist or blacklist addresses and repoint the privileged _safeOwner address, and _mint lets the owner create more supply into its own balance. The code shows how sells can be blocked; no transaction showing a blocked sale was opened for this record.
How it happened
- The constructor sets
_owner = owner;and_safeOwner = owner;and mints the whole supply to the owner through_mint(_owner, initialSupply*(10**18));. transferandtransferFromboth call_approveCheck(...), which is declaredinternal burnTokenCheck(sender,recipient,amount), so the modifier runs on every holder-to-holder move.- The owner is exempt. While
_owner == _safeOwner, the first transfer sent by the owner runs_safeOwner = recipient;and passes. After that, transfers pass wheneversender == _owner || sender == _safeOwner || recipient == _owner. - An ordinary holder (not whitelisted, not blacklisted) falls to the last branch.
_sellAmountstarts at 0, soamount < _sellAmountis false and the code reachesrequire((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");. Since the holder is not_safeOwner, the transfer only succeeds if the recipient is_unirouter. Any other recipient reverts. The same check applies to whichever address is the sender, including a pool contract paying out tokens, unless that address is whitelisted or is_safeOwner. - The owner can change who is exempt.
increaseAllowance(address[] receivers)(owner only) sets_whiteAddress[...] = true, which skips all checks for those senders.addApprove(address[] receivers)sets_blackAddress[...] = true, which limits those senders torecipient == _unirouter.decreaseAllowance(address safeOwner)sets_safeOwner.multiTransferwhitelists the firstapprovecountreceivers and sets a maximum allowance for_unirouteron their behalf. - If the owner sends tokens to itself,
_sellAmount = amount;is set (once_safeOwneris no longer the owner). From then on a non-privileged transfer smaller than_sellAmountpasses, and if its recipient is_safeOwnerthe sender is blacklisted by_blackAddress[sender] = true;. - No function in the contract changes
_owneror renounces it, and_mintispublicwith onlyrequire(msg.sender == _owner, ...), so the owner can add to its own balance at any time. Whether any of these powers were used is not shown by the code.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.