zkSync Copycat Rug Pull

Reported loss $202K
Rugpull

What happened

zkSync (TBD) is a copycat BEP-20 token on BNB Chain that reuses the zkSync name; it is not the real zkSync project. The verified source contains an owner-controlled trading switch and an allowance bug that make selling through a router fail for ordinary holders. De.Fi's REKT database lists a rug pull on 2023-07-28 in which the deployer removed liquidity from the PancakeSwap pool in two transactions, with a loss of 201,549 USD (822.65 BNB); the listing says the proceeds were sent to another EOA and then split across several addresses.

CertiK Alert flagged the same day a large liquidity removal on the token by the deployer, put at about 731 WBNB (about 176K USD), and called it a honeypot scam. Deployer: 0x1cd9af6a…ef9a8b. Recipient address in the De.Fi listing: 0xa52f62dc…907594.

Liquidity removal transactions in the listing: 0x65a6f4f5…b23552 and 0xf748b082…4a9606.

How it happened

  1. The constructor mints the whole 1,000,000,000 supply to the deployer and makes it owner: _balances[msg.sender] = _totalSupply; and owner = msg.sender;.
  2. approve is broken for everyone except the owner. _approve ignores its _owner argument and writes to the state variable owner: _allowances[owner][spender] = amount;. A holder who approves a router therefore changes the deployer's allowance, not their own.
  3. A router sell calls transferFrom(holder, pair, amount), which does _transfer(sender, recipient, amount) and then require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance") against _allowances[sender][_msgSender()]. That entry is never set for a normal holder, so the sell reverts. Buys, where the pair sends tokens with transfer, are not affected. The deployer's own allowance is set by every approve call, so the deployer can sell through a router.
  4. The owner also holds a trading switch. RenounceOwnership(address _DEAD, bool _boo) only sets safeTransfer = _boo (it does not renounce anything), and _transfer requires require(safeTransfer || _balances1[sender], ...). With safeTransfer false, only addresses whitelisted by Prize_Fund can send tokens, and _approve has the same check on msg.sender.
  5. Other owner powers: burn(address account, uint256 amount) onlyOwner actually mints: _totalSupply += amount; _balances[account] += amount;. changeOwner and OwnershipRenounce both just assign a new owner. SetBurnPercent and SetCharityPercent have no cap, but the burn percentage does not take tokens from the recipient: _transfer computes the local uint256 _burnAmount = amount * burnPercent / 100 ; yet deducts the never-assigned state variable, amount = amount - charityAmount - burnAmount;, which stays 0, so the recipient is credited the full amount. The local _burnAmount only shrinks the supply counter, _totalSupply -= _burnAmount;, with no matching balance change, so totalSupply() drifts below the sum of balances, and a large enough burnPercent would make that subtraction revert and block transfers.
  6. What was reported: De.Fi's listing says the deployer removed liquidity in two transactions on 2023-07-28 and moved the funds to another address. CertiK Alert called the removal about 731 WBNB (about 176K USD) and the token a honeypot. Which of the code controls above were exercised on-chain was not checked.

Protocol details

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

Build your security review skills

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