SafeCompound Honeypot

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

  1. The constructor sets _owner = owner; and _safeOwner = owner; and mints the whole supply to the owner through _mint(_owner, initialSupply*(10**18));.
  2. transfer and transferFrom both call _approveCheck(...), which is declared internal burnTokenCheck(sender,recipient,amount), so the modifier runs on every holder-to-holder move.
  3. The owner is exempt. While _owner == _safeOwner, the first transfer sent by the owner runs _safeOwner = recipient; and passes. After that, transfers pass whenever sender == _owner || sender == _safeOwner || recipient == _owner.
  4. An ordinary holder (not whitelisted, not blacklisted) falls to the last branch. _sellAmount starts at 0, so amount < _sellAmount is false and the code reaches require((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.
  5. 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 to recipient == _unirouter. decreaseAllowance(address safeOwner) sets _safeOwner. multiTransfer whitelists the first approvecount receivers and sets a maximum allowance for _unirouter on their behalf.
  6. If the owner sends tokens to itself, _sellAmount = amount; is set (once _safeOwner is no longer the owner). From then on a non-privileged transfer smaller than _sellAmount passes, and if its recipient is _safeOwner the sender is blacklisted by _blackAddress[sender] = true;.
  7. No function in the contract changes _owner or renounces it, and _mint is public with only require(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

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

Build your security review skills

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