Rocket Vault Honeypot

Reported loss Not disclosed
Ethereum
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

  1. The constructor stores the owner argument as _owner and _safeOwner and mints the supply to it: _owner = owner; _safeOwner = owner; _mint(_owner, initialSupply*(10**18));.
  2. transfer and transferFrom both call _approveCheck(sender, recipient, amount), which carries the burnTokenCheck(sender,recipient,amount) modifier. Nothing moves unless that modifier reaches its _;.
  3. Owner side: while _owner == _safeOwner, the first branch if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;} catches every transfer out of _owner and makes its recipient the new _safeOwner. Once _safeOwner differs from _owner, if (sender == _owner || sender == _safeOwner || recipient == _owner) lets _owner and _safeOwner send anywhere, and lets anyone send to _owner. The owner can also reassign _safeOwner with decreaseAllowance(address safeOwner).
  4. 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 runs if (amount < _sellAmount), and _sellAmount starts at 0, so the test is false and the else branch applies the same require. Because sender == _safeOwner was already handled in step 3, in practice the recipient has to be _unirouter, so a holder's transfer to any other address reverts.
  5. The owner can set _sellAmount with a self-transfer, if (sender == _owner && sender == recipient){_sellAmount = amount;}, but only once _safeOwner differs 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 _safeOwner has been moved to another address, by a first transfer out or decreaseAllowance, unlisted senders can move amounts below _sellAmount, and when the recipient is _safeOwner the sender is added to _blackAddress.
  6. The owner manages the lists with increaseAllowance(address[]) (whitelists and unblacklists), addApprove(address[]) (blacklists and unwhitelists), and multiTransfer, which whitelists the first approvecount receivers and sets their allowance for _unirouter to the maximum.
  7. _mint(address account, uint256 amount) is public and owner-only, and it credits _balances[_owner] whatever account is, so the owner can increase the supply.

Protocol details

Classification Token
Protocol Type Onchain Capital Allocator
Affected asset / contract RVF
Protocol links Website @RCKTFoundation

Build your security review skills

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