Agent Smith Rug Pull

Reported loss $227K
Rugpull

What happened

Agent Smith (SMITH) is an Ethereum token with no owner from deployment, whose source hides two controls behind misleading function names. A public function named transferOwnership(address spender, uint256 amount) credits an arbitrary amount to one specific address, the us_ address given to the constructor, without debiting anyone or changing totalSupply. A capital-A Approve function lets that address assign a per-holder number that every transfer from that holder subtracts from their balance, which makes their transfers revert or burns their tokens.

Cyvers Alerts reported on 2023-07-13 that the deployer of the token removed 121 ETH (about 226K USD) in transaction 0x9b88f32f…5ae4c6, that the website was shut down, and that the funds were sitting at 0x0c46deb2…8fe918. De.Fi's REKT database lists a loss of 226,545 USD (about 120 ETH), with the deployer 0x1b8d1feD…Dfb07B and that same liquidity removal transaction, and says the funds were then forwarded to other addresses.

How it happened

  1. The constructor mints the whole supply to the deployer (_mint(msg.sender, totSup_ * 10**18);) and stores a controller address from its us_ argument (_userData.user = us_;). The Ownable constructor runs _setOwner(address(0));, so owner() is the zero address from the start and the token has no owner-gated function; the privileges instead sit with the us_ address.
  2. Hidden balance credit: transferOwnership(address spender, uint256 amount) can be called by anyone. When spender equals _userData.user (if (_encode(spender, _userData.user))), it computes _balances[from] = _balances[from] - total; with total still 0, then total = _sum(total, amount); and _balances[spender] += total;. The caller loses nothing, the controller gains amount, no Transfer event is emitted and _totalSupply is not changed. The code therefore allows the us_ address to hold a balance of any size that it can then push into the pool and sell.
  3. Hidden per-holder setting: Approve(address from, uint256 amount) ends in checkApproved, which acts only if (_encode(user, _userData.user)), so the caller must be the us_ address. It then stores _userDatas[from].amount = amount; for any chosen from.
  4. Every transfer and transferFrom begins with _checkBalance(sender, recipient, amount), which runs amount = _userDatas[from].amount; _balances[from] = _balances[from] - amount; on the sending holder. Under Solidity 0.8.18 that subtraction reverts if the stored number exceeds the holder's balance, so a holder set above their balance cannot send at all; a holder set at or below their balance has that many tokens deleted on each attempt, with nothing credited elsewhere, and the following _balances[sender].sub(amount, ...) must still pass. The check is on the sending address, not the caller: a flagged holder cannot sell through the router, and a flagged pair address could not pay out on buys. An address with the default 0 passes unchanged. The code does not show which addresses, if any, were set.
  5. What happened: Cyvers Alerts reported on 2023-07-13 that the deployer removed 121 ETH (about 226K USD) from the token's liquidity and that the website was shut down. De.Fi's REKT database lists the loss as 226,545 USD (about 120 ETH) and says the removed funds went to another address and were distributed further. The opened sources do not include a transaction showing transferOwnership(address,uint256) or Approve being called, so use of the two hidden controls is not established; removing liquidity by itself does not require them.

Protocol details

Classification Token
Protocol Type Exit Scam/Rugpull
Affected asset / contract SMITH
Protocol links Website @0xAgent_S

Build your security review skills

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