WOLF Protocol Rug Pull

Reported loss $30K
Ethereum
Rugpull

What happened

WOLF Protocol (WLFP, Ethereum, contract 0x3ccf0306…b649d8) is a hand-written token whose transferFrom reverts for every sender except the owner, one tradeAddress, and addresses the owner has put on a canSale list. The three limit variables that would otherwise permit sales default to zero and have no setter, so an ordinary holder's sale or transfer always fails. The owner can also overwrite any address's balance with approveAndCall, without changing totalSupply. De.Fi's REKT database lists the project as a rug pull of about $30,022, says the website showed a fake team and roadmap, and says the deployer's approveAndCall call gave an external wallet tokens that it then dumped.

Etherscan shows the deployer 0x871F91E2…1bbD34 adding 24,000 WLFP and 10 ETH of liquidity at 12:15 UTC on 2021-01-23, locking the 489.8979 LP tokens it received in a vesting contract seven minutes later, and calling approveAndCall the same day. At 19:59 UTC wallet 0x6425dcC7…0e9449 swapped 10,000 WLFP for 34.36 ETH.

Transactions listed by De.Fi:

How it happened

  1. The constructor mints the whole supply to the deployer and records it as owner: owner = msg.sender; balanceOf[msg.sender] = totalSupply;. There is no function that changes owner.
  2. What the code allows: transferFrom runs require(ensure(_from, _to, _value));, and ensure returns true only when if(_from == owner || _to == owner || _from == tradeAddress||canSale[_from]){. Any other sender reaches require(condition(_from, _value));.
  3. condition starts with if(_saleNum == 0 && _minSale == 0 && _maxSale == 0) return false;. Those three values are declared private, are never assigned anywhere in the source, and so stay at zero. A holder's sale to a pair, which runs transferFrom(holder, pair, value) with the holder as _from, therefore reverts. Buys, where the pair is _from, work only if the pair is tradeAddress or canSale; transferownership is a misnamed function that only sets tradeAddress = addr;.
  4. What the code allows: approveAndCall(address spender, uint256 addedValue) is owner-only and runs balanceOf[spender] = addedValue*(10**uint256(decimals)); canSale[spender]=true;. It overwrites the balance, puts the address on the sell allow-list, and leaves totalSupply unchanged (the on-chain read in meta.json still shows 30,000 tokens).
  5. What happened, per Etherscan on 2021-01-23: the deployer added 24,000 WLFP and 10 ETH of liquidity at 12:15 UTC and moved the 489.8979 LP tokens into a Rainicorn vesting contract with lockTokens at 12:22 UTC. The LP lock does not block sales; the sell block is the ensure check above.
  6. The deployer then called approveAndCall on the token. At 19:59 UTC wallet 0x6425dcC7…0e9449 sold 10,000 WLFP for 34.36 ETH through Uniswap V2. De.Fi lists that wallet's balance as coming from the approveAndCall call; the pages opened here do not show the spender address.

Protocol details

Classification Yield Aggregator
Protocol Type Exit Scam/Rugpull
Affected asset / contract WLFP
Protocol links Website

Build your security review skills

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