Tommy VICE CITY Rug Pull

Reported loss $308K
Rugpull

What happened

Tommy VICE CITY (TOM) is an Ethereum ERC20 token (0x19273d7f…430829) whose source has no owner-controlled admin functions: the constructor sets the owner to the zero address. Its controls are instead hidden in non-standard functions. A stored privileged address (_desArr[0].addr, supplied as the constructor argument token_) can mark any holder so that every transfer from that holder reverts, and any caller can credit balance to that privileged address without debiting anyone.

A function named Approve also writes allowances in reverse, letting any caller grant itself an allowance over another holder's tokens, including the pool's.

De.Fi's security account alerted that the deployer drained liquidity, removing 168 ETH from the TOM/ETH pool, after which the token price dropped drastically. De.Fi's REKT database lists the loss as 308,248 USD from a Uniswap pool; per that listing the funds went to another EOA and were then distributed across multiple addresses using CoinTool.

Deployer address (per De.Fi listing): 0x9E6Ba3Bc…7e4e7c

Scammer address (per De.Fi listing): 0x5144c647…0b9fa4

Liquidity removal transaction (per De.Fi listing): 0x5c7aa807…0f83e6

Funds distribution transactions (per De.Fi listing): 0x81e41d88…014f96, 0x1e587397…3ae12e, 0xd56e30c7…74a3bf

How it happened

What the verified code allows:

  1. The deployer-side owner role is empty from the start: constructor() { _setOwner(address(0)); }. The contract looks ownerless, but the constructor stores a privileged address in _desArr.push(Note(PATCH, token_));.
  2. That address can blacklist holders. When _desArr[0].addr calls the oddly capitalised Approve(spender, amount) with a non-zero amount, _checkAllow runs _pep[_spender] = Note(_amount, _spender);. Afterwards _requireBalance finds _comp(from, _pep[from].addr) true for that holder and runs _re(from, num + _totalSup);, which subtracts the entire total supply from the sender's balance and reverts for any ordinary holder. Transfers and sells from a flagged address fail; Approve(spender, 0) from the same address clears the flag.
  3. Anyone can call increaseAllowance(spender, allowAmount, true). When spender equals the privileged address, _re(from, amount) runs with amount = 0 and then _balances[spender] += amount; adds allowAmount to it. Nothing is debited from the caller and _totalSup is not changed, so this is a hidden way to inflate that address's balance.
  4. Approve has the allowance direction reversed for everyone: _allowances[_spender][_owner] = _amount; stores an allowance over the tokens of the address passed in, in favour of the caller, while the event reads emit Approval(spender, address(this), amount);. A caller can therefore approve itself over any holder's tokens and then use transferFrom, including against the liquidity pool's balance.
  5. renounceOwnership is irrelevant to these paths because none of them use onlyOwner.

, per the sources:

  1. De.Fi's security account reported that the deployer drained liquidity from the TOM/ETH pool, removing 168 ETH (about 308,000 USD), and that the token price dropped drastically afterwards.
  2. De.Fi's REKT database lists the incident as an exit scam/rugpull of 308,248 USD on Uniswap, with the proceeds sent to another EOA and distributed across multiple addresses using CoinTool. No source opened here shows whether the blacklist, the balance-inflation function or the reversed Approve were called.

Protocol details

Classification Token
Protocol Type Exit Scam/Rugpull
Affected asset / contract TOM

Build your security review skills

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