Smoothy Finance Honeypot
What happened
Smoothy.Finance (SMTY) at 0x698974aa…83dd9c on Ethereum is an ERC20 whose transfer and transferFrom both pass through a burnTokenCheck modifier. _owner and _safeOwner can send freely, and so can whitelisted addresses. With the default _sellAmount of 0, any other holder's transfer goes through only if the recipient is _owner or the hard-coded address _unirouter (0x7a250d56…F2488D); a transfer to any other recipient reverts with the misleading message ERC20: transfer amount exceeds balance.
The owner can also whitelist or blacklist addresses at will. De.Fi's REKT database lists the token as an Exit Scam/Honeypot and describes it as the contract owner being able to disable the transfer function, which restricted users in selling their tokens. No transaction or loss figure is stated in that listing.
How it happened
What the code allows:
- The constructor stores the passed-in
owneras both_ownerand_safeOwnerand mints the initial supply to it. No function in the contract changes_ownerafterwards, and there is norenounceOwnership. - While
_owner == _safeOwner, the first transfer sent by the owner takes the first branch of the modifier:if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}. Whichever address receives that first owner transfer becomes_safeOwner. - Otherwise the modifier first checks
if (sender == _owner || sender == _safeOwner || recipient == _owner). Those transfers always pass: the owner and_safeOwnercan send anywhere, and any sender can send to_owner. In that branch,if (sender == _owner && sender == recipient){_sellAmount = amount;}lets the owner set the threshold used in step 6. - Any other sender (not
_owner, not_safeOwner, sending to someone other than_owner) is checked next. A whitelisted sender passes viaif (_whiteAddress[sender] == true){ _;}. A blacklisted sender passes only ifrequire((sender == _safeOwner)||(recipient == _unirouter), ...)holds, which for this sender means the recipient is_unirouter. - A sender who is on neither list, with
_sellAmountstill 0, reaches}else{require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");_;}. The holder's balance is intact, but the transfer reverts unless it goes to_unirouter(or, by step 3, to_owner). A swap that pulls tokens into a pair contract sends them to the pair, not to the router address, so therequirefails for that holder. - If
_sellAmounthas been set above 0, a non-listed sender sending less than_sellAmountis allowed through to any recipient, but if the recipient is_safeOwnerthe sender is added to_blackAddressby_blackAddress[sender] = true; _whiteAddress[sender] = false;. A non-listed sender sending_sellAmountor more gets the samerequireas in step 5. A blacklisted sender can afterwards send only to_unirouteror_owner. - The owner controls the exemptions:
increaseAllowance(address[])sets_whiteAddresstrue (and_blackAddressfalse);addApprove(address[])does the reverse;decreaseAllowance(address safeOwner)sets_safeOwnerto any address; andmultiTransferwhitelists the firstapprovecountreceivers and sets a maximum allowance in their name for_unirouter. _mintispublicbut restricted byrequire(msg.sender == _owner, ...)and always credits_balances[_owner], so the owner can increase supply to itself.
De.Fi's REKT database lists the token as an Exit Scam/Honeypot. No transaction showing the owner calling any of these functions was opened.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.