Rocket Vault Honeypot
What happened
Rocket Vault (RVF) is an Ethereum token whose transfers run through a burnTokenCheck modifier that sorts senders into owner, whitelist and blacklist groups. With the default _sellAmount of 0, a sender who is on no list can only send tokens to the owner or to the hard-coded _unirouter address; a transfer from such a holder to any other address, such as a trading pair, reverts with "ERC20: transfer amount exceeds balance". The owner can whitelist and blacklist addresses and mint new tokens.
De.Fi's REKT database lists the token as an exit scam/honeypot and says the contract owner could disable the transfer function, which restricted users in selling their tokens.
How it happened
- The constructor stores the
ownerargument as_ownerand_safeOwnerand mints the supply to it:_owner = owner; _safeOwner = owner; _mint(_owner, initialSupply*(10**18));. transferandtransferFromboth call_approveCheck(sender, recipient, amount), which carries theburnTokenCheck(sender,recipient,amount)modifier. Nothing moves unless that modifier reaches its_;.- Owner side: while
_owner == _safeOwner, the first branchif (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}catches every transfer out of_ownerand makes its recipient the new_safeOwner. Once_safeOwnerdiffers from_owner,if (sender == _owner || sender == _safeOwner || recipient == _owner)lets_ownerand_safeOwnersend anywhere, and lets anyone send to_owner. The owner can also reassign_safeOwnerwithdecreaseAllowance(address safeOwner). - Everyone else: a whitelisted sender passes. A blacklisted sender must satisfy
require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"). A sender on neither list runsif (amount < _sellAmount), and_sellAmountstarts at 0, so the test is false and theelsebranch applies the samerequire. Becausesender == _safeOwnerwas already handled in step 3, in practice the recipient has to be_unirouter, so a holder's transfer to any other address reverts. - The owner can set
_sellAmountwith a self-transfer,if (sender == _owner && sender == recipient){_sellAmount = amount;}, but only once_safeOwnerdiffers from_owner. While they are equal, the first branch in step 3 catches the self-transfer and only sets_safeOwner = recipient(the owner again), so that line is never reached. After_safeOwnerhas been moved to another address, by a first transfer out ordecreaseAllowance, unlisted senders can move amounts below_sellAmount, and when the recipient is_safeOwnerthe sender is added to_blackAddress. - The owner manages the lists with
increaseAllowance(address[])(whitelists and unblacklists),addApprove(address[])(blacklists and unwhitelists), andmultiTransfer, which whitelists the firstapprovecountreceivers and sets their allowance for_unirouterto the maximum. _mint(address account, uint256 amount)ispublicand owner-only, and it credits_balances[_owner]whateveraccountis, so the owner can increase the supply.
Protocol details
Evidence
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.