SolstarterToken Honeypot

Reported loss Not disclosed
Ethereum
Honeypot

What happened

SolstarterToken (SOS) is an ERC-20 whose transfer and transferFrom both run through a burnTokenCheck modifier. Owner-set roles decide who can move tokens: the owner, the owner-set _safeOwner and whitelisted addresses pass, while every other sender can only send to the hard-coded _unirouter address (0x7a250d56…F2488D) until the owner sets a threshold. The owner can also blacklist holders, change _safeOwner and call _mint again.

De.Fi's REKT database lists the token as an exit scam/honeypot and states that the contract owner could disable the transfer function, which restricted users in selling their tokens. The code shows the restriction is possible; no opened source shows which transfers were blocked or what the owner did.

How it happened

  1. The constructor sets _owner = owner; _safeOwner = owner; and then calls _mint 30 times with initialSupply*(10**18), once for _owner and 29 times naming five other addresses. _mint ignores its account argument for the balance: _balances[_owner] = _balances[_owner].add(amount); emit Transfer(address(0), account, amount);, so the balance goes to _owner while the events name other addresses.
  2. The first transfer made by the owner while _owner == _safeOwner runs if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}, so _safeOwner becomes that transfer's recipient. The code does not say which address that is. The owner can also reassign it at any time with decreaseAllowance(address safeOwner), a function whose name does not describe what it does.
  3. For any other sender, burnTokenCheck lets the transfer through only in these cases: sender == _owner || sender == _safeOwner || recipient == _owner; the sender is in _whiteAddress; the amount is below _sellAmount; or the final else{require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");_;} passes. _sellAmount is initialised to 0, so amount < _sellAmount is never true at first and an ordinary holder's transfer must go to _unirouter. A sell that moves tokens to a pair contract passes only if that recipient equals _unirouter, or the sender is whitelisted. Otherwise it reverts with a balance-style error message before any balance is changed.
  4. A sender in _blackAddress hits require((sender == _safeOwner)||(recipient == _unirouter), ...) and is limited to the same router-only path. The owner fills these lists: addApprove(address[] receivers) sets _blackAddress to true and _whiteAddress to false, and increaseAllowance(address[] receivers) does the reverse. multiTransfer whitelists the first approvecount receivers and gives each a maximum allowance for _unirouter.
  5. Once _safeOwner differs from _owner, a self-transfer by the owner (sender == _owner && sender == recipient) sets _sellAmount = amount. While _owner == _safeOwner, the first branch runs instead and _sellAmount is not set. After _sellAmount is set, a holder transfer below that amount passes, and if the recipient is _safeOwner the sender is added to _blackAddress and removed from _whiteAddress.
  6. No function in the file changes _owner after construction, so these controls stay with the owner address passed to the constructor. What happened on-chain is not shown by the code: De.Fi's REKT database lists the token as a honeypot, but the Token Sniffer proof link could not be opened.

Protocol details

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

Build your security review skills

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