Pills for Sale Honeypot

Reported loss $447K
Ethereum
Honeypot

What happened

PILLS (on-chain name pills.forsale) was an Ethereum token traded against WETH on Uniswap V2. De.Fi's REKT database lists it as an exit scam and honeypot with 447,069 USD lost, saying the contract owner could disable the transfer function, which stopped users from selling. It lists the deployer's initial liquidity transaction, 0x80464b80…0d0585, and the deployer's liquidity removal transaction, 0xbd5e38b1…3f9904. The removal transaction page shows a removeLiquidityETH call on Uniswap V2 Router 2 from the deployer 0x17912c92…5EaA13 on 2021-01-17 at 09:15 UTC, burning 46,957.43 LP tokens for about 3,869,895 PILLS and 571.89 ETH.

There is no single pause switch in the verified code. Instead, the owner has several controls over who can move tokens: a blacklist whose transfers silently destroy the sender's balance, a sell fee computed by an external contract, per-address exemptions that skip allowance checks, and an arbitrary delegatecall. Which of these were used on holders is not shown by the source or by the transactions opened.

How it happened

What the code allows:

  1. The constructor receives a recipient list b and calls block(b), then computes the Uniswap V2 pair address UNI = pairFor(weth, address(this)); and gives the deployer all supply plus a maximum router allowance: allowance[msg.sender][router] = uint256(-1);.
  2. block(address[] memory _to) onlyOwner sets blacklist[to] = true and paid = true for each address in the list. The owner can add any address, including the pair, at any time.
  3. In transferFrom, the sender's balance is reduced first and the blacklist check follows: balanceOf[_from] -= _value; if (blacklist[_to] || blacklist[_from]) {return true;}. For a blacklisted sender or recipient the call returns success, but nothing is credited to the recipient and no Transfer event is emitted, so the tokens are destroyed. A sale into the pair by a blacklisted holder would leave the pair with no input.
  4. For a transfer into the pair by a non-owner, non-developer address once paid is true, the fee is not fixed in the token: if (paid && _to == UNI && _from != owner && developer[_from] == 0) { return Management(manager).getFee(address(this), UNI, _value); }. The sender is debited the full _value while the recipient is credited _value - fee, so the fee is simply removed from the pair's credit. The fee amount comes from an external contract at 0x0F450Ddc…A22Fa6 whose code is not in the supplied source. Under compiler 0.5.17 there is no overflow check on _value - fee.
  5. batchSend (owner only) credits tokens and sets developer[to] = 1. A developer address skips the fee, and if (msg.sender != _from && developer[tx.origin] == 0) { require(allowance[_from][msg.sender] >= _value); means a transaction started by a developer address skips the allowance check, so it can pull tokens from any holder with transferFrom.
  6. delegate(address a, bytes memory b) onlyOwner runs a.delegatecall(b);, which lets the owner execute arbitrary code against the token's own storage, including balances.

, per the sources:

  1. De.Fi's listing says the owner could disable transfers, which stopped users from selling, and that the deployer added the initial liquidity and later removed it.
  2. The opened Etherscan page for the removal transaction confirms the deployer called removeLiquidityETH on 2021-01-17 and received about 3,869,895 PILLS and 571.89 ETH for 46,957.43 LP tokens.

Protocol details

Classification Token
Protocol Type Exit Scam/Honeypot
Affected asset / contract PILLS
Protocol links Website

Build your security review skills

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