Foxy.farm Rug Pull
What happened
FOXY (foxy.farm) is an ERC-20 token whose mint(address _to, uint256 _amount) is restricted to the owner but has no supply cap, so the owner address can create any number of tokens at any time. Etherscan shows the deployer address 0x164ff155…1d2261 added 5 ETH and 1,000,000 FOXY of liquidity on Uniswap V2 on 2020-10-19 (tx 0xc9271907…77f2fd). On 2020-10-20 the same address called mint to create 1,000,000,000 FOXY for itself (tx 0x1780a928…3fd478) and about 95 seconds later swapped them on Uniswap V2 for roughly 21.86 WETH (tx 0x35cbcf78…69e71b).
The code also gives the owner a transfer gate it can tighten (checkValidAddress, checkValidContract) and an owner-only burn(address _from, uint256 _amount) that can destroy any holder's balance; none of the opened sources shows those two being used.
How it happened
- The deployer address 0x164ff155…1d2261 deployed the token on 2020-10-19 (tx 0x8ad85da8…913d89) and is its owner:
contract FoxyFarm is ERC20("foxy.farm", "FOXY"), Ownable. - Shortly afterwards it added 5 ETH and 1,000,000 FOXY to a new Uniswap V2 pair, so the pool's token side was only 1,000,000 FOXY.
- The owner-only mint has no cap and no timelock:
function mint(address _to, uint256 _amount) public onlyOwner { holders[_to] = true; _mint(_to, _amount); }. The comment above it calls it a rewards mint. It also adds the recipient to theholderswhitelist that the transfer gate checks. - On 2020-10-20 at 17:48:29 UTC the deployer called
mintfor 1,000,000,000 FOXY to its own address, 1,000 times the amount it had put into the pool. - At 17:50:04 UTC the deployer sold all of it with
swapExactTokensForETHand received about 21.86 WETH. Selling a token amount that large into a pool holding 1,000,000 FOXY returns most of the pool's ETH side (constant-product arithmetic, not read from pool state). - What the code additionally allows, not shown as used:
function checkValidAddress(uint256 _addr) public onlyOwnersetscontractLength, andisNotBotreturns(size == 0) && contractLength == INT_RANGE, so once the owner setscontractLengthto any value other than 65535,checkValidBalance(isNotBot(sender) || holders[sender] || holder < 1) fails for every non-whitelisted sender after the firsttransfercall. The owner whitelists addresses withcheckValidContract, andfunction burn(address _from, uint256 _amount) public onlyOwner { _burn(_from, _amount); }needs no allowance.
Protocol details
Evidence
- report Report t.me
- report FoxyFarm source (Sourcify repository) repo.sourcify.dev
- transaction Etherscan transaction: addLiquidityETH for FOXY etherscan.io
- transaction Etherscan transaction: mint of 1,000,000,000 FOXY etherscan.io
- transaction Etherscan transaction: swapExactTokensForETH of 1,000,000,000 FOXY etherscan.io
- address FoxyFarm verified source (Etherscan code tab) etherscan.io
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.