Gains Associates Honeypot
What happened
De.Fi's REKT database lists Gains Associates (GAINS) on Ethereum as an Exit Scam/Honeypot, noting that the contract owner could disable the transfer function and so stop users selling. The verified source at 0x96a72e21…8e5707 does not switch every transfer off. Each transfer and transferFrom runs through a burnTokenCheck modifier under which, by default, a holder's tokens can only be sent to one hard-coded address, _unirouter; a transfer to any other destination reverts with ERC20: transfer amount exceeds balance even when the balance is sufficient.
The owner can whitelist or blacklist addresses and repoint the privileged _safeOwner, and the owner-only multiTransfer moves tokens with the unchecked _transfer. The code shows how sales can be blocked; no transaction showing a blocked sale was opened for this record.
How it happened
- The constructor sets
_owner = owner;and_safeOwner = owner;and mints the whole supply to the owner through_mint(_owner, initialSupply*(10**18));. transferandtransferFromboth call_approveCheck(...), declaredinternal burnTokenCheck(sender,recipient,amount), so the modifier runs on every holder-to-holder move.- The owner is exempt. While
_owner == _safeOwner, the first transfer sent by the owner runs_safeOwner = recipient;and passes. After that, transfers pass wheneversender == _owner || sender == _safeOwner || recipient == _owner. - An ordinary holder (not whitelisted, not blacklisted) falls to the last branch.
_sellAmountstarts at 0, soamount < _sellAmountis false and the code reachesrequire((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");. Since the holder is not_safeOwner, the transfer only succeeds if the recipient is_unirouter; any other recipient reverts. The same check applies to whichever address is the sender, including a pool contract paying out tokens, unless that address is whitelisted or is_safeOwner. - The owner can change who is exempt.
increaseAllowance(address[] receivers)sets_whiteAddress[...] = true, which skips all checks for those senders.addApprove(address[] receivers)sets_blackAddress[...] = true, limiting those senders torecipient == _unirouter.decreaseAllowance(address safeOwner)sets_safeOwner. multiTransfercalls_transfer(msg.sender,receivers[i], amounts[i]);directly, which skipsburnTokenCheck, then whitelists the firstapprovecountreceivers and sets a maximum allowance for_unirouteron their behalf.- If the owner sends tokens to itself (once
_safeOwneris no longer the owner),_sellAmount = amount;is set. A non-privileged transfer smaller than_sellAmountthen passes, and if its recipient is_safeOwnerthe sender is blacklisted by_blackAddress[sender] = true;. - No function changes or renounces
_owner, and_mintstayspublicbehindrequire(msg.sender == _owner, ...), so the owner can add to its own balance later. Whether any of these powers were used is not shown by the code.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.