Solstarter Honeypot

Reported loss Not disclosed
Ethereum
Honeypot

What happened

Solstarter (SOS) is an Ethereum token whose transfer and transferFrom both run through a burnTokenCheck modifier. For a holder who is neither whitelisted nor blacklisted, that modifier only lets tokens move to the hard-coded _unirouter address 0x7a250d56…F2488D or to the owner, unless the amount is below _sellAmount, which starts at 0. A transfer to any other address, such as a DEX pair, reverts with "ERC20: transfer amount exceeds balance".

The owner alone controls who is whitelisted or blacklisted and which address counts as _safeOwner. De.Fi's REKT database lists SOS as an exit scam/honeypot dated 2021-03-27 and describes the owner as able to disable the transfer function, so holders could not sell; it lists no loss figure. The contract was deployed in transaction 0x4b2c270a…f95e11 on that day.

How it happened

  1. The constructor stores the supplied owner as both _owner and _safeOwner: _owner = owner; _safeOwner = owner;, then mints the initial supply to the owner.
  2. Every holder transfer goes through _approveCheck(sender, recipient, amount), which carries the burnTokenCheck modifier. transfer calls it with the caller as sender, and transferFrom calls it with the supplied sender.
  3. Privileged paths pass. While _owner == _safeOwner, the first transfer sent by the owner runs _safeOwner = recipient and goes through. After that, any transfer where sender == _owner || sender == _safeOwner || recipient == _owner goes through. A holder who sends tokens back to the owner is therefore never blocked.
  4. Whitelisted senders pass (if (_whiteAddress[sender] == true){ _;}). A blacklisted sender is limited to require((sender == _safeOwner)||(recipient == _unirouter), ...), so only transfers to _unirouter succeed.
  5. A sender on neither list reaches if (amount < _sellAmount). _sellAmount is initialised to 0, so the branch cannot be taken and the else branch runs: require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"). A holder's transfer to any address other than _unirouter or the owner reverts, and a revert leaves the holder's balance unchanged. A router-based sell moves tokens from the holder to the pair, so it fails this check unless the holder is whitelisted or the pair is the owner or _unirouter.
  6. The owner can change the outcome per address. increaseAllowance(address[]) whitelists and un-blacklists receivers, addApprove(address[]) blacklists and un-whitelists them, decreaseAllowance(address) replaces _safeOwner, and multiTransfer whitelists the first approvecount receivers and gives them a maximum allowance to _unirouter. The owner sending an amount to itself runs _sellAmount = amount, after which smaller transfers by unlisted holders skip the require; if such a transfer goes to _safeOwner, the sender is blacklisted by _blackAddress[sender] = true.
  7. _mint(address account, uint256 amount) is public but requires msg.sender == _owner; it increases _totalSupply and credits _balances[_owner], so the owner can create more tokens.
  8. These are capabilities in the code. The explorer transactions that would show which addresses were whitelisted or blacklisted, or whether _mint was called, were not 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.