Pills for Sale 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:
- The constructor receives a recipient list
band callsblock(b), then computes the Uniswap V2 pair addressUNI = pairFor(weth, address(this));and gives the deployer all supply plus a maximum router allowance:allowance[msg.sender][router] = uint256(-1);. block(address[] memory _to) onlyOwnersetsblacklist[to] = trueandpaid = truefor each address in the list. The owner can add any address, including the pair, at any time.- 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 noTransferevent is emitted, so the tokens are destroyed. A sale into the pair by a blacklisted holder would leave the pair with no input. - For a transfer into the pair by a non-owner, non-developer address once
paidis 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_valuewhile 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. batchSend(owner only) credits tokens and setsdeveloper[to] = 1. A developer address skips the fee, andif (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 withtransferFrom.delegate(address a, bytes memory b) onlyOwnerrunsa.delegatecall(b);, which lets the owner execute arbitrary code against the token's own storage, including balances.
, per the sources:
- 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.
- The opened Etherscan page for the removal transaction confirms the deployer called
removeLiquidityETHon 2021-01-17 and received about 3,869,895 PILLS and 571.89 ETH for 46,957.43 LP tokens.
Protocol details
Evidence
- transaction Etherscan transaction 0xbd5e38b18dba9eaf6f43e343ee0e8394cf59dc24dbb26c49d3d7f6994e3f9904 (removeLiquidityETH) etherscan.io
- transaction etherscan.io transaction 0x8046…0585 etherscan.io
- address pills.forsale (PILLS) StandardToken contract source, Etherscan etherscan.io
- analysis Sourcify match for 0x38710a11b17ca49362f461cbe2713491716b4b2b on chain 1 repo.sourcify.dev
- analysis De.Fi REKT database listing for PILLS de.fi
- analysis Condenses AI (Bittensor subnet) website condenses.ai
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.