Dasani Honeypot

Reported loss Not disclosed
BNB Chain
Honeypot

What happened

Dasani (DASANI) is a BNB Smart Chain token whose transfers all pass through a burnTokenCheck modifier. Once the owner has made a first outgoing transfer, any holder who is not the owner, not the stored _safeOwner and not whitelisted can only send tokens to the hard-coded _unirouter address 0x05fF2B0D…608C7F or to the owner, unless the amount is below _sellAmount, which is 0 until the owner sets it. A sell that ends in a pool address is therefore rejected, with an error message that talks about balance although the holder's balance is untouched.

The owner can also whitelist, blacklist and re-point _safeOwner, and can mint more tokens. De.Fi's REKT database lists the token as an exit scam/honeypot and says the contract owner could disable the transfer function, which restricted users in selling their tokens. No transaction or holder data was opened for this token, so the code shows what the owner could do, not what was done.

How it happened

  1. The constructor sets the owner and mints the whole supply to it: _owner = owner; _safeOwner = owner; _mint(_owner, initialSupply*(10**18));. Nothing in the contract changes _owner afterwards.
  2. Every transfer and transferFrom runs _approveCheck(...), which carries the burnTokenCheck modifier. Its first branch is if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}, so the recipient of the owner's first outgoing transfer becomes _safeOwner.
  3. Owner and _safeOwner as senders, and anyone sending to the owner, pass: if (sender == _owner || sender == _safeOwner || recipient == _owner){.
  4. Every other sender who is not whitelisted reaches require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");. Since such a sender is never _safeOwner here, the transfer only succeeds when the recipient is _unirouter. A sell that moves tokens into a pool pair through transferFrom has the pair as recipient and reverts. The balance is only debited after the modifier passes, so a revert leaves the holder's balance unchanged. A buy, where the pair is the sender, passes only if the pair is the owner, _safeOwner or whitelisted; _safeOwner is whatever the owner's first outgoing transfer was sent to.
  5. The one gap is if (amount < _sellAmount). _sellAmount starts at 0 (uint256 private _sellAmount = 0;) and is only set when the owner transfers to itself: if (sender == _owner && sender == recipient){_sellAmount = amount;}. When a smaller transfer does pass and its recipient is _safeOwner, the sender is blacklisted: if(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;}. A blacklisted sender is held to the same recipient rule as step 4.
  6. The owner controls the lists. increaseAllowance(address[] memory receivers) whitelists addresses (_whiteAddress[receivers[i]] = true;), addApprove blacklists them, decreaseAllowance(address safeOwner) replaces _safeOwner, and multiTransfer whitelists the first approvecount receivers and approves the maximum amount to _unirouter for them. The function names do not describe these effects. A whitelisted sender passes the modifier with no recipient restriction.
  7. _mint is public but owner-only and credits the owner, not its account argument: require(msg.sender == _owner, "ERC20: mint to the zero address"); ... _balances[_owner] = _balances[_owner].add(amount);. The code places no cap on further minting by the owner.

Protocol details

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

Build your security review skills

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