BitLord Rug Pull

Reported loss $566K
Rugpull

What happened

Bitlord (BITLORD) is a hand-written ERC-20 whose supply is minted through an owner-only function named removeLimits, whose Transfer events do not match the real balance changes, and which includes a hard-coded address that can move any holder's balance to the zero address. On 2023-08-05, about 15 hours after deployment, Etherscan shows the deployer address 0x870C07aD…292D90 calling removeLiquidityETHWithPermit on Uniswap V2 Router 2 and withdrawing 308.99 ETH from the BITLORD/ETH pool in transaction 0x78db4a91…ba7d07. De.Fi's alert describes this as a rug pull of about 567,000 USD (its listing says 566,201 USD) and adds that the price stayed stable because the token appears to be a honeypot.

De.Fi's listing also states the withdrawn funds were swapped to WETH and moved to another externally owned account, 0x837fbff1…18f124; that transfer was not opened here.

How it happened

  1. The contract hard-codes the owner and a team address: address public _Owner = 0x870C07aD0B092A38b72bD9d6Bd2E5304a7292D90; and team_addr = 0x808877B83A698F0d866E69591fDCA106069e6d69;. There is no function that changes _Owner, and renounceOwnership() and lock() are declared public _onlyOwner(){} with empty bodies, so neither gives up or restricts anything.
  2. The constructor mints the whole supply, 690,000,000,000,000 tokens with 18 decimals, by calling removeLimits(team_addr, initialSupply*(10**18)). The function credits the owner, not its locker argument: _balances[_Owner] = _balances[_Owner].add(amt); emit Transfer(address(0), locker, amt);. It is also callable by the owner at any later time, so the owner can mint more.
  3. _transfer rewrites the event as well: if (sender == _Owner){sender = team_addr;}if (recipient == _Owner){recipient = team_addr;} runs before emit Transfer(sender, recipient, amount);. Tokens held and moved by the deployer therefore appear on explorers as belonging to team_addr.
  4. airdrop(address ad, address[] eReceiver, uint256[] eAmounts) (owner only) and execute(address uPool, address[] eReceiver, uint256[] eAmounts) (only the hard-coded address 0x9bfA2DD3…F77C3B) loop over emit Transfer(ad, eReceiver[i], eAmounts[i]); and change no balances, so they can make airdrops or pool-to-wallet transfers appear on explorers that never happened.
  5. Approve(address[] recipients) is not an approval: for each address it runs uint256 amt = _balances[recipients[i]]; _balances[recipients[i]] = ... .sub(amt, ...); _balances[address(0)] = _balances[address(0)].add(amt);, restricted by _apprvrOnly to 0x9bfA2DD3…F77C3B. That address can therefore set any holder's balance to zero and credit the amount to the zero address. The code has no sell block, blacklist or fee; ordinary transfers are plain balance moves.
  6. What happened, per Etherscan: on 2023-08-05 at 18:06:47 UTC the deployer called removeLiquidityETHWithPermit on Uniswap V2 Router 2 (tx 0x78db4a91…ba7d07), burning its UNI-V2 liquidity tokens and withdrawing 308.99 ETH plus the paired BITLORD. Removing liquidity needs no special token code, only that the deployer held the liquidity tokens. No transaction opened here shows Approve, execute or airdrop being called.

Protocol details

Classification Token
Protocol Type Exit Scam/Rugpull
Affected asset / contract BITLORD

Build your security review skills

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