MoneyWulf Honeypot
What happened
MoneyWulf (Wulf) is a BEP20 token on BNB Smart Chain, deployed on 2021-06-20 as contract 0xe69382ba…39abd5. De.Fi's REKT database lists it as an exit scam / honeypot in which the owner could stop holders from selling. The verified source shows a switch called tracing, on by default, that makes transfer and transferFrom revert when the recipient equals a stored address nxOwner.
nxOwner is filled in automatically from the first transferFrom, so if that transfer is the one into the liquidity pair, sells through the pair revert while buys still work. The contract also has an onlyOwner function that credits the caller's balance with any amount.
How it happened
- The constructor gives the deployer the full supply:
balances[owner] = _totalSupply;with_totalSupply = 69*10**15anddecimals = 0. The state variablebool tracing = true;means the recipient checks are active from deployment. transfercarries therequestHandlermodifier:if(tracing) require(to != nxOwner, "Handling Request");.transferFromcarries thecontextHandlermodifier:if(from != address(0) && nxOwner == address(0)) nxOwner = to; else require(to != nxOwner, "Order ContextHandler");. WhilenxOwneris zero, the recipient of the firsttransferFromis stored in it and that call succeeds. If the firsttransferFromis the transfer into the liquidity pair, the pair becomesnxOwner. The code does not show which transaction set it on-chain.- A buy pays out of the pair with
transfer(buyer, ...), so the recipient is notnxOwnerand it passes. A sell has the router calltransferFrom(holder, pair, ...), soto == nxOwnerand the call reverts with "Order ContextHandler" whiletracingis true. No function other than that first capture ever writesnxOwner. - The modifier
onlyOwnerisrequire(msg.sender == owner || msg.sender == nxOwner);, andsetTracing(bool _tracing) onlyOwnercan switch both checks off or back on. The owner can therefore lift or restore the block at will; the code does not show whether it did. emitTransfer(uint256 _input) public onlyOwnerrunsbalances[msg.sender] = balances[msg.sender] + (_input);without changing_totalSupply, so the owner can credit itself tokens that were never part of the stated supply and emit a mint-styleTransferevent from the zero address.renounceOwnership(address _newOwner) publichas no access control and only writescurrentOwner, a variable no other function reads, so it does not give up the owner role;owneris never reassigned after the constructor._construct()sends the contract's own BNB balance to the owner. No source opened here shows liquidity being removed or tokens being sold.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.