Capellix Honeypot
What happened
Capellix (CPLX) is a BEP20 token on BNB Smart Chain whose transfer and transferFrom are wrapped in modifiers that revert any transfer to a stored address, prevOwner, while the ownerLock flag is on. The flag is set to true in the constructor and is changed with setOwnerLock, which is guarded by onlyOwner; that modifier passes when _owner == _msgSender() and also when _owner == prevOwner. The first transferFrom made while prevOwner is empty stores that call's recipient as prevOwner, and transferOwnership (also onlyOwner) overwrites it without changing _owner.
De.Fi's REKT database lists Capellix as an Exit Scam/Honeypot in which the contract owner could disable transfers and so restrict holders from selling.
How it happened
- The constructor calls
Initialize(), which setsownerLock=trueand credits the whole supply (_maxSupply = 1 * 10**8 * 10**9) to the deployer. transfercarriestransferMiddleware(recipient,ownerLock). WhileownerLockis true it runsrequire(to != prevOwner, "Handling Request"), so any direct transfer toprevOwnerreverts.transferFromcarriestransferFromMiddleware(sender, recipient, ownerLock). WhileownerLockis true:if(from != address(0) && prevOwner == address(0)) prevOwner = to; else require(to != prevOwner, "Order TransferFromMiddleware");. The firsttransferFromtherefore stores its recipient inprevOwner, and every latertransferFromto that address reverts. The code does not show which address that first recipient was.- If
prevOwneris the trading pair, a sell that moves tokens to the pair reverts, while other transfers and buys are not touched by this check. onlyOwnerisrequire((_owner == _msgSender() || _owner == prevOwner), "Ownable: caller is not the owner");. The owner passes it. So does anyone else whenever_owner == prevOwner, for example if the first captured recipient was the owner's own address.transferOwnership(address newOwner)only runsprevOwner = newOwnerand does not change_owner, so an owner can pointprevOwnerat an unused address to lift the block, or ataddress(0)so the nexttransferFromre-captures a recipient.setOwnerLock(bool)switches the check off or back on.renounceOwnership()sets_owner = address(0). After that,onlyOwnerpasses for every caller ifprevOwneris stilladdress(0), because_owner == prevOwnerthen holds, so anyone could callsetOwnerLockandtransferOwnership. IfprevOwnerholds another address, no caller passes. The code alone does not show whether or when any of these functions was called; De.Fi lists the token as a honeypot.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.