OliveOptions Honeypot
What happened
OliveOptions (Olive) at 0xe84b5db2…04047c is a BNB Smart Chain token whose transfer hook, burnTokenCheck, lets the owner, one owner-chosen address (_safeOwner), anyone sending to the owner, and owner-whitelisted wallets send tokens without restriction. A holder outside those groups can only send to the hard-coded _unirouter address, so a sell into the liquidity pair, whose recipient is the pair rather than _unirouter, reverts with "ERC20: transfer amount exceeds balance". The owner controls the whitelist and blacklist and can also mint to their own balance.
De.Fi's REKT database lists the token as an exit scam/honeypot in which the owner could disable the transfer function and stop users from selling.
How it happened
- The constructor stores
ownerin both_ownerand_safeOwnerand calls_mint(_owner, initialSupply*(10**18)). Because_mintrequiresmsg.sender == _owner, the owner argument must be the deploying address. Every transfer, includingtransferFrom, runs through_approveCheck, which carries theburnTokenCheck(sender,recipient,amount)modifier. - The first branch of the modifier is
if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}. The first transfer sent by the owner therefore moves_safeOwnerto its recipient. If that transfer is the liquidity deposit made through a router, the pair becomes_safeOwner; the source does not show which address it ended up as. - Next:
if (sender == _owner || sender == _safeOwner || recipient == _owner)always lets the call through, andif (sender == _owner && sender == recipient){_sellAmount = amount;}records an amount when the owner sends to themselves. The owner, the_safeOwneraddress and anyone sending to the owner are never blocked. - Every other sender: a whitelisted sender (
_whiteAddress) passes. A blacklisted sender must satisfyrequire((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"); since such a sender is not_safeOwner, only transfers to the hard-coded_unirouteraddress succeed. - A sender on neither list reaches
if (amount < _sellAmount){ ... }else{require((sender == _safeOwner)||(recipient == _unirouter), ...)}._sellAmountstarts at0, soamount < _sellAmountis false and therequireapplies. A router sell callstransferFrom(holder, pair, amount)with the pair as recipient, which is neither_unirouternor sent by_safeOwner, so it reverts before_balances[sender]is reduced. Wallet-to-wallet transfers by such holders fail the same way. - If
_sellAmountis set to a positive value, a sell smaller than it passes but runsif(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;}, which blacklists the seller when the recipient is_safeOwner; sells at or above that amount revert. - The owner decides who is exempt:
increaseAllowance(address[] receivers)whitelists and un-blacklists,addApprove(address[] receivers)blacklists,decreaseAllowance(address safeOwner)replaces_safeOwner, andmultiTransfer(approvecount, receivers, amounts)whitelists the firstapprovecountrecipients and sets their allowance to the router to the maximum._mint(address account, uint256 amount)ispublic, checks onlymsg.sender == _owner, and addsamountto_balances[_owner]and_totalSupply. There is no ownership transfer or renounce function. No transaction showing the owner calling any of these was opened, so the source shows what the owner could do, not what was done.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.