Yearn Hold Finance Honeypot
What happened
Yearn Hold Finance (YHFI) is an Ethereum ERC-20 whose _transfer calls a hidden gate, _ints, that one address controls: the owner passed to the constructor and stored as _address0. When that address calls ints(addressn) with any address other than itself, every transfer that does not involve the owner must come from the new _address1, from the Uniswap V2 router, or from an address the owner whitelisted, and every other transfer reverts with a message that reads like a zero-address error. A holder selling through the router is the sender in transferFrom(holder, pair, amount), so the sale reverts. De.Fi's REKT database lists the token as a honeypot in which the owner could disable the transfer function, and no opened transaction shows whether ints was called.
Etherscan shows the following on 2020-12-09. The deployer address 0x1510d0C3…104c4d added 70 ETH and 880,000 YHFI to a new Uniswap V2 pool at 12:11:15 UTC (transaction 0xe7415dcb…0b9337) and, about six hours later at 17:59 UTC, removed liquidity with removeLiquidityETHWithPermit for 620,865.58 YHFI and 99.33 ETH (transaction 0xa48abc76…31db1e).
How it happened
- The constructor stores the same address twice,
_address0 = owner; _address1 = owner;, and mints the whole supply to it with_mint(_address0, initialSupply*(10**18));. This is the only mint in the contract. While the two variables are equal the gate does nothing, because it requires_address0!=_address1. - Only
_address0can arm the gate:function ints(address addressn) public { require(msg.sender == _address0, "!_address0");_address1 = addressn; }. There is no cap, delay or event, and nothing in the contract prevents calling it repeatedly. - Once
_address1differs from_address0,_transferruns_ints:if(recipient != _address0 && sender != _address0 && _address0!=_address1 && amount > _zero){require(sender == _address1 ||sender==_router || _Addressint[sender]. A holder selling through the Uniswap router is thesenderoftransferFrom(holder, pair, amount). The holder is not the owner, not_address1, not the router and not whitelisted, so the call reverts. Setting_address1to the pair address would let buys pass (the pair is then an allowed sender) while holder sales still revert. - Transfers where the sender or recipient is
_address0skip the check, so the owner can still move tokens directly. Transfers sent by the router also pass (sender==_router). A transfer from the pair to the router, which is the first hop when liquidity is removed through the router, passes only if the pair is_address1or is whitelisted, so the owner can choose to keep the pair able to send while holders cannot. multiaddress(AllowN, receivers, amounts)lets the owner send tokens to chosen addresses and, for the firstAllowNof them, set_Addressint[receivers[i]] = true;and_approve(receivers[i], _router, _valuehash);. Those addresses are allowed senders even when the gate is armed, and the router holds an unlimited allowance over their balances.- What happened, per Etherscan: the deployer address added liquidity at 12:11 UTC and removed it at 17:59 UTC on 2020-12-09, receiving 99.33 ETH and 620,865.58 YHFI back. That is a liquidity withdrawal through the router, not a call into token code. The code allows the gate described in steps 2 to 4, but no transaction opened for this record shows
intsbeing called.
Protocol details
Evidence
- report @CaptainJackAPE incident report twitter.com
- report YHFI source on Sourcify repo.sourcify.dev
- transaction Etherscan transaction 0xe7415dcb... (add liquidity) etherscan.io
- transaction Etherscan transaction 0xa48abc76... (remove liquidity) etherscan.io
- address YHFI verified source on Etherscan etherscan.io
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.