Harvester Network Honeypot
What happened
HARVESTER restricts every transfer with a hard-coded rule, not a switch: the ensure modifier only lets a transfer through if the sender is the owner, the recipient is the owner, or the sender is the token's Uniswap V2 pair. A holder selling into the pair is a transfer from the holder to the pair, which fails that rule, so only the owner address can sell, while buys (pair to buyer) and the owner's liquidity removal pass. Etherscan shows the deployer address 0x484cC1De…67f82c added 9 ETH and 50,000 HARVESTER of liquidity on 2020-12-17 (tx 0x9b7ab0b3…aab29b) and on 2020-12-23 removed liquidity with removeLiquidityETHWithPermit, receiving 22,073.2 HARVESTER and 20.44 ETH (tx 0x8648805c…85a039).
The source contains no function that turns the restriction off or on; it applies from deployment.
How it happened
- The constructor makes the deployer the owner and gives it the whole supply, plus an unlimited router allowance:
owner = msg.sender;/balanceOf[msg.sender] = totalSupply;/allowance[msg.sender][0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D] = uint(-1);. - Every transfer goes through
transfer, which callstransferFrom(msg.sender, _to, _value), andtransferFromcarries the modifierensure(_from, _to). ensurecomputes the Uniswap V2 pair address for this token and WETH and requires:require(_from == owner || _to == owner || _from == UNI);where the localUNIis that pair. Tokens can therefore leave the pair (buys, liquidity removal), be sent by the owner, or be sent to the owner.- A holder's sale, whether a direct
transferto the pair or a routertransferFrom(holder, pair, amount), has the holder as_fromand the pair as_to, so none of the three conditions hold and the call reverts. Holder-to-holder transfers fail the same way. - The deployer added 9 ETH and 50,000 HARVESTER on 2020-12-17. On 2020-12-23 it removed the liquidity and received 20.44 ETH and 22,073.2 HARVESTER; that is 11.44 ETH more than it had added, consistent with ETH paid into the pool by other buyers (arithmetic from the two Etherscan pages).
- The owner also has
function delegate(address a, bytes memory b) public payable { require(msg.sender == owner); a.delegatecall(b); }, which executes arbitrary code against the token's own storage, so the owner could alter balances or the owner variable; no use of it is shown in the opened sources.
Protocol details
Evidence
- report Report harvester.network
- report HARVESTER source (Sourcify repository) repo.sourcify.dev
- transaction Etherscan transaction: addLiquidityETH for HARVESTER etherscan.io
- transaction Etherscan transaction: removeLiquidityETHWithPermit for HARVESTER etherscan.io
- address HARVESTER verified source (Etherscan code tab) etherscan.io
- analysis Web Archive web.archive.org
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.