LIGMA Honeypot

Reported loss Not disclosed
Ethereum
Honeypot

What happened

De.Fi's REKT database lists LIGMA ($BALLS, Ethereum, contract 0x61395d8c…1322d0) as an Exit Scam/Honeypot dated 2021-05-22 and says the owner could disable the transfer function so holders could not sell. The verified source has no single on/off switch. Instead every transfer passes through the burnTokenCheck modifier, which lets the owner, a variable called _safeOwner, and transfers to the owner pass.

A holder who is neither whitelisted nor blacklisted can send tokens to the hard-coded _unirouter address, to the owner, or to any recipient when the amount is below _sellAmount (which starts at 0). Other transfers revert with "ERC20: transfer amount exceeds balance". The source does not state what _unirouter is.

The owner controls the whitelist, the blacklist, _safeOwner and, through a self-transfer, _sellAmount.

How it happened

  1. The constructor sets _owner = owner; and _safeOwner = owner; and mints the initial supply to _owner.
  2. Every transfer and transferFrom runs _approveCheck(...), which carries the burnTokenCheck modifier. Its first branch is if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}: while the owner is still _safeOwner, a transfer sent by the owner passes and moves _safeOwner to that transfer's recipient.
  3. Once _safeOwner is no longer the owner, the second branch lets _owner, _safeOwner, or any transfer to _owner through. For other senders, a whitelisted sender passes. A blacklisted sender must satisfy require((sender == _safeOwner)||(recipient == _unirouter), ...). A sender in neither list passes when amount < _sellAmount; otherwise the same require applies.
  4. _sellAmount starts at 0 (uint256 private _sellAmount = 0;), so amount < _sellAmount is never true until the owner sets it. The owner can set it with a transfer to itself, if (sender == _owner && sender == recipient){_sellAmount = amount;}, but only once _safeOwner is no longer the owner: while _owner == _safeOwner, a self-transfer takes the first branch and does not reach that line.
  5. With _sellAmount at 0, a non-listed holder can therefore send tokens to _unirouter (0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D, hard-coded; the source does not say what it is) or to _owner. A transfer to any other recipient, such as a pair address, reverts with "ERC20: transfer amount exceeds balance". The revert undoes the balance change, so the holder keeps the tokens but cannot send them there.
  6. The owner-only functions are named misleadingly: increaseAllowance(address[] receivers) whitelists, addApprove(address[] receivers) blacklists, decreaseAllowance(address safeOwner) sets _safeOwner, and multiTransfer(...) whitelists the first approvecount receivers and approves _unirouter for the maximum amount. Whether any address was whitelisted, and whether a pair was _safeOwner, is not shown by the code alone.
  7. _mint(address account, uint256 amount) is public and gated only by require(msg.sender == _owner, ...); it adds to _balances[_owner], so the owner can mint more supply.

Protocol details

Classification Token
Protocol Type Exit Scam/Honeypot
Affected asset / contract BALLS

Build your security review skills

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