Hype Bet Rug Pull

Reported loss $35K
Ethereum
Rugpull

What happened

Hype.Bet is an ERC20 on Ethereum whose transfer hook (burnTokenCheck) lets the deployer and one tracked address move tokens freely while every other holder's transfer reverts unless the owner has whitelisted them. In its default state the pool can send tokens out (buys work) but a holder's sale to the pool reverts, so ETH put into the pool by buyers can only be taken back by the owner side. De.Fi's REKT database lists the token as an exit scam / rug pull and records the deployer adding initial liquidity at 0x0010c4e0…2124bf and removing it at 0xd2492fe5…08a76d.

Etherscan shows the first as an addLiquidityETH call from the deployer address 0x2cf915Bc…976ca4 on 2020-11-29 at 03:35:36 UTC (300 ETH and 120,000 Hype.Bet for 6,000 UNI-V2 tokens), and the second as a removeLiquidityETHWithPermit call from the same address at 10:39:17 UTC the same day, burning 6,000 UNI-V2 tokens and returning 360.55 WETH and 99,929.41 Hype.Bet.

How it happened

  1. The constructor mints the supply to the owner and records the same address as the safe owner: _owner = owner; _safeOwner = owner; _mint(_owner, initialSupply*(10**18));.
  2. While _owner == _safeOwner, the first transfer the owner makes re-points the safe owner at the recipient: if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}. If that first transfer is the liquidity deposit, the Uniswap pair becomes _safeOwner. Etherscan shows the owner-side addLiquidityETH call sending 120,000 tokens to the pair; the transactions alone do not show that this was the owner's first transfer, so this step is what the code allows.
  3. Buys pass: tokens leaving the pair have sender == _safeOwner, so they hit the branch if (sender == _owner || sender == _safeOwner || recipient == _owner){ ... _;} and the transfer runs.
  4. Sells revert: a holder selling through the router calls transferFrom(holder, pair, amount), so sender is the holder and recipient is the pair. A holder who is not whitelisted falls to if (amount < _sellAmount){ ... }else{require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");_;}. _sellAmount starts at 0, so the first test is false, and the recipient is the pair rather than the router, so the require reverts before any balance is debited. The holder keeps the tokens but cannot sell them.
  5. The owner decides who escapes: increaseAllowance(address[] memory receivers) (owner only) sets _whiteAddress[receivers[i]] = true, multiTransfer whitelists the first approvecount receivers and gives them a maximum router allowance, and whitelisted senders go straight to if (_whiteAddress[sender] == true){ _;} and can sell. addApprove (owner only) sets _blackAddress[receivers[i]] = true and clears the whitelist for chosen addresses. Setting _sellAmount by an owner self-transfer would let smaller sales through, but any such seller whose recipient is _safeOwner is then blacklisted: if(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;}.
  6. The owner can also create supply: function _mint(address account, uint256 amount) public { require(msg.sender == _owner, ...); _totalSupply = _totalSupply.add(amount); _balances[_owner] = _balances[_owner].add(amount); has no cap and credits the owner. The source has no renounceOwnership or ownership transfer, so _owner stays fixed.
  7. What happened, per Etherscan: the deployer address deposited 300 ETH and 120,000 tokens at 03:35:36 UTC and received 6,000 UNI-V2 tokens. At 10:39:17 UTC the same address burned 6,000 UNI-V2 tokens through removeLiquidityETHWithPermit and received 360.55 WETH and 99,929.41 tokens, 60.55 WETH more than it deposited. De.Fi's REKT database lists the incident as an exit scam / rug pull.

Protocol details

Classification Token
Protocol Type Exit Scam/Rugpull
Affected asset / contract Hype.Bet
Protocol links Website

Build your security review skills

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