OliveOptions Honeypot

Reported loss Not disclosed
BNB Chain
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

  1. The constructor stores owner in both _owner and _safeOwner and calls _mint(_owner, initialSupply*(10**18)). Because _mint requires msg.sender == _owner, the owner argument must be the deploying address. Every transfer, including transferFrom, runs through _approveCheck, which carries the burnTokenCheck(sender,recipient,amount) modifier.
  2. The first branch of the modifier is if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}. The first transfer sent by the owner therefore moves _safeOwner to 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.
  3. Next: if (sender == _owner || sender == _safeOwner || recipient == _owner) always lets the call through, and if (sender == _owner && sender == recipient){_sellAmount = amount;} records an amount when the owner sends to themselves. The owner, the _safeOwner address and anyone sending to the owner are never blocked.
  4. Every other sender: a whitelisted sender (_whiteAddress) passes. A blacklisted sender must satisfy require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"); since such a sender is not _safeOwner, only transfers to the hard-coded _unirouter address succeed.
  5. A sender on neither list reaches if (amount < _sellAmount){ ... }else{require((sender == _safeOwner)||(recipient == _unirouter), ...)}. _sellAmount starts at 0, so amount < _sellAmount is false and the require applies. A router sell calls transferFrom(holder, pair, amount) with the pair as recipient, which is neither _unirouter nor sent by _safeOwner, so it reverts before _balances[sender] is reduced. Wallet-to-wallet transfers by such holders fail the same way.
  6. If _sellAmount is set to a positive value, a sell smaller than it passes but runs if(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;}, which blacklists the seller when the recipient is _safeOwner; sells at or above that amount revert.
  7. The owner decides who is exempt: increaseAllowance(address[] receivers) whitelists and un-blacklists, addApprove(address[] receivers) blacklists, decreaseAllowance(address safeOwner) replaces _safeOwner, and multiTransfer(approvecount, receivers, amounts) whitelists the first approvecount recipients and sets their allowance to the router to the maximum. _mint(address account, uint256 amount) is public, checks only msg.sender == _owner, and adds amount to _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

Classification Token
Protocol Type Exit Scam/Honeypot
Affected asset / contract Olive

Build your security review skills

Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.