Solstarter 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
- The constructor stores the supplied owner as both
_ownerand_safeOwner:_owner = owner; _safeOwner = owner;, then mints the initial supply to the owner. - Every holder transfer goes through
_approveCheck(sender, recipient, amount), which carries theburnTokenCheckmodifier.transfercalls it with the caller as sender, andtransferFromcalls it with the suppliedsender. - Privileged paths pass. While
_owner == _safeOwner, the first transfer sent by the owner runs_safeOwner = recipientand goes through. After that, any transfer wheresender == _owner || sender == _safeOwner || recipient == _ownergoes through. A holder who sends tokens back to the owner is therefore never blocked. - Whitelisted senders pass (
if (_whiteAddress[sender] == true){ _;}). A blacklisted sender is limited torequire((sender == _safeOwner)||(recipient == _unirouter), ...), so only transfers to_uniroutersucceed. - A sender on neither list reaches
if (amount < _sellAmount)._sellAmountis initialised to 0, so the branch cannot be taken and theelsebranch runs:require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"). A holder's transfer to any address other than_unirouteror 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. - 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, andmultiTransferwhitelists the firstapprovecountreceivers 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 therequire; if such a transfer goes to_safeOwner, the sender is blacklisted by_blackAddress[sender] = true. _mint(address account, uint256 amount)ispublicbut requiresmsg.sender == _owner; it increases_totalSupplyand credits_balances[_owner], so the owner can create more tokens.- These are capabilities in the code. The explorer transactions that would show which addresses were whitelisted or blacklisted, or whether
_mintwas called, were not opened.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.