FractionalArt Honeypot
What happened
FractionalArt (FRCT) is an Ethereum token whose transfer and transferFrom both run through an owner-controlled burnTokenCheck modifier. An address that is on neither of the owner's lists can only send tokens to the hard-coded _unirouter address (0x7a250d56…F2488D) or to the owner, unless the amount is below an owner-set _sellAmount; every other transfer reverts with ERC20: transfer amount exceeds balance. De.Fi's REKT database lists FractionalArt as an Exit Scam/Honeypot dated 2021-04-10 and says the owner could disable the transfer function, which restricted users in selling their tokens.
The code has no single on/off switch for transfers; the restriction comes from the whitelist, blacklist and _sellAmount checks.
How it happened
- The first transfer the owner sends while
_safeOwnerstill equals_ownerpasses and stores its recipient:if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}. The code does not show who that recipient was. - Transfers whose sender is
_owneror_safeOwner, or whose recipient is_owner, always pass. An owner transfer to itself also writes the threshold:if (sender == _owner && sender == recipient){_sellAmount = amount;}. - Any other sender found in
_whiteAddresspasses unchecked. The owner fills that list throughincreaseAllowance(address[]), and through the firstapprovecountreceivers ofmultiTransfer, which also grants_uniroutera maximum allowance over their tokens. - A sender in
_blackAddress, which the owner fills throughaddApprove(address[]), must satisfyrequire((sender == _safeOwner)||(recipient == _unirouter), ...). The_safeOwnerhalf is already handled by step 2, so in practice the recipient must be_unirouter. - Every other holder falls to the last branch. If
amount < _sellAmountthe transfer runs, and whenrecipient == _safeOwnerthe sender is moved onto the blacklist in that same call. Otherwise the samerequireapplies and the transfer reverts unless the recipient is_unirouter(or the owner, via step 2). While_sellAmountis at its initial value of 0 the threshold branch can never run. A router sale moves tokens withtransferFromto the pair address, so it passes only if the pair happens to be one of the addresses these branches accept; the code never names the pair. - The owner can re-point
_safeOwnerat any time withdecreaseAllowance(address safeOwner), and_mintispublicwith only amsg.sender == _ownercheck and no supply cap. There is no function that transfers or renounces_owner. The constructor calls_mintten times; each call credits_balances[_owner]while theTransferevent names theaccountargument, so the two hard-coded addresses in the constructor appear in events but their allotments are credited to_owner.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.