Goldfinch Finance Honeypot

Reported loss Not disclosed
Ethereum
Honeypot

What happened

De.Fi's REKT database lists Goldfinch Finance (GFF) on Ethereum as an exit scam / honeypot, stating that the contract owner could disable the transfer function and so restrict users from selling their tokens. The verified source for 0xd8eb25fb…a30ec4 has no single on/off switch of that name. It restricts transfers through the burnTokenCheck modifier: unless an address is whitelisted, a holder's transfer reverts unless it goes to the address stored in _unirouter, and only the owner can whitelist, blacklist, or change the exempt _safeOwner.

A swap-style sell, which sends tokens to a pair rather than to that address, is therefore blocked for ordinary holders by default. The listing gives no loss figure and no transaction, so what happened on the chain is taken from De.Fi's listing only.

How it happened

  1. The constructor stores _owner = owner; and _safeOwner = owner; and then makes five _mint(..., initialSupply*(10**18)) calls. Inside _mint, the line _balances[_owner] = _balances[_owner].add(amount); credits the owner regardless of the account argument, while the Transfer event names that account (0xa1D8d972…0a3fBf, 0xD551234A…2192FF, 0x2FAF487A…776AD2 and 0x32Be343B…102D88 for the four calls after the first). _mint is public but restricted by require(msg.sender == _owner, ...), so the owner can mint again later.
  2. Every transfer and transferFrom runs _approveCheck, which carries the burnTokenCheck modifier. Its first branch is if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}: the recipient of the owner's first transfer becomes _safeOwner. If that first transfer is the liquidity deposit, the exchange pair becomes _safeOwner.
  3. Privileged senders pass: if (sender == _owner || sender == _safeOwner || recipient == _owner){ ... _;}. A transfer sent from the pair when it is _safeOwner (a buy) passes this way. A self-transfer by the owner also sets the sell threshold: if (sender == _owner && sender == recipient){_sellAmount = amount;}.
  4. An ordinary holder (not whitelisted, not blacklisted) reaches if (amount < _sellAmount){ ... }else{require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");_;}. _sellAmount starts at 0, so amount < _sellAmount is false for every amount, and the holder's transfer reverts unless the recipient is the address stored in _unirouter (0x7a250d56…F2488D). A sell that moves tokens to the pair fails with the misleading message "transfer amount exceeds balance".
  5. If the owner raises _sellAmount, a holder who sells less than that amount to _safeOwner is let through but is marked by if(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;}. A blacklisted holder hits require((sender == _safeOwner)||(recipient == _unirouter), ...) on every later transfer, so cannot sell again.
  6. Only the owner can change the lists: increaseAllowance(address[]) sets _whiteAddress[...] = true and _blackAddress[...] = false, addApprove(address[]) does the reverse, and decreaseAllowance(address safeOwner) sets _safeOwner. multiTransfer whitelists its first approvecount receivers and approves _unirouter for the maximum amount. The function names suggest ERC20 allowance helpers but they control the lists.
  7. The source has no function that changes _owner, so these powers stay with that address. The source shows these powers; it does not show which addresses were whitelisted, blacklisted or what _sellAmount was. De.Fi's REKT database lists the token as an exit scam / honeypot.

Protocol details

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

Build your security review skills

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