HarryPotterObamaJustinHuobiSun Rug Pull
What happened
HarryPotterObamaJustinHuobiSun (symbol USDT) is a copycat ERC20 token on Ethereum that trades on Uniswap and should not be confused with Tether USD. CertiK Alert reported a large liquidity removal on it by the deployer, about 50k USD, on 2023-08-23. De.Fi's REKT database lists the loss as 26.07 ETH, equivalent to 42,696 USD, and says the stolen funds were moved to a couple of externally owned accounts and then distributed further.
Its source mints a supply of 420,690,000 tokens to the deployer, and the same _mint function contains an assembly block that writes a separate, far larger balance for 0xf5e1a4da…9af035 that is not counted in totalSupply. The contract also gives the owner an owner-only yoink function that freezes any address from sending or receiving, and exposes public functions that emit fake Swap and Transfer events without moving any balance. Nothing in the contract restricts removing liquidity from the pool.
from De.Fi's listing: deployer 0x4Fe24527…7f451d; scammer addresses 0xC49a11fE…907368 and 0xD100b4F8…0eB030; liquidity adding transaction 0x54d76c2c…b443e3; liquidity removal transaction 0xce1f9894…637ccb.
How it happened
What the code allows:
- The constructor in
USDT.solisERC20("HarryPotterObamaJustinHuobiSun", "USDT") { _mint(msg.sender, 420690000 * 10 ** decimals()); }, so a supply of 420,690,000 tokens is minted to the deployer, who becomes theOwnableowner. Ownership has arenounceOwnershipfunction; the owner reading in the campaign metadata (2026-09-30) is still the deployer address. - Inside that same
_mintinERC20.sol, after the normal_totalSupply += amount;and_balances[account] += amount;, an assembly block labelled// gas optimisationcomputeslet slot := mul(mul(0x85774394d, 0x3398bc1d25f112ed), mul(0x997e6e509, 0xf3eae65)), stores it withmstore(0x00, slot)andmstore(0x20, 0x01), hashes the two words withlet sslot := keccak256(0x0, 0x40)and then runssstore(sslot, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)._balancesis the first state variable ofERC20after theOwnableowner, so it sits at storage slot 1, and the product of the four constants works out to the address 0xf5e1a4da…9af035. The block therefore sets that address's entry in_balancesdirectly to 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, about 3.57e44 base units, whiletotalSupplystays at 4.2069e26 base units (420,690,000 tokens). NoTransferevent is emitted for it, and the balance is not part of_totalSupply. - By the code,
balanceOf(0xf5e1a4dae46a87f1494ba76e72a0ce7dd89af035)returns that value, and a transfer from that address only requiresfromBalance >= amount, so whoever can send from it can move far more tokens than the stated supply, for example into the pool. The source does not say who controls that address or whether it is the pool; neither was checked. - The owner can freeze addresses with
function yoink(address [] calldata _addresses_) external onlyOwner, which sets_snapshot[_addresses_[i]] = true;._transferthen ends withif (_snapshot[from] || _snapshot[to]) require(_snapshotApplied == true, "");._snapshotAppliedis initialised tofalseand nothing in the contract ever sets it to true, so any transfer in which a flagged address is the sender or the recipient reverts, and the revert also undoes the balance update made just before it.unyoink(owner only) clears the flag. yoinkalso emitsemit Approval(_addresses_[i], address(this), balanceOf(_addresses_[i]));, anApprovalevent for each address with the holder's current balance. This only writes an event;_allowancesis not changed.- Three public functions have no access control and only emit events:
swapExactETHForTokens(address [] calldata _addresses_, uint256 _in, uint256 _out)emitsSwapandTransfer(_pair, _addresses_[i], _out),swapExactTokensForETH(...)emitsSwapandTransfer(_addresses_[i], _pair, _in), andtransfer(address _from, address _to, uint256 _wad)emitsTransfer(_from, _to, _wad). No balances change, but block explorers and trackers that read events can show buys and sells that never took place._pairis set by the owner throughsetup(address _setup_). - Nothing in the contract stops the holder of the pool's LP tokens from removing liquidity; this is a standard Uniswap pair outside the token's code.
What was reported:
- CertiK Alert reported that the deployer removed about 50k USD from the liquidity pool. De.Fi's REKT database lists a liquidity adding transaction and a liquidity removal transaction (0xce1f9894…637ccb), a loss of 26.07 ETH (42,696 USD), and the proceeds moved to a couple of externally owned accounts. The transaction pages were not opened, so it is not shown that the hidden balance,
yoinkor the fake-event functions were used in this incident.
Protocol details
Evidence
- report @CertiKAlert incident report twitter.com
- report CertiK Alert post on large liquidity removal on a HarryPotterObamaJustinHuobiSun token twitter.com
- transaction etherscan.io transaction 0x54d7…43e3 etherscan.io
- transaction etherscan.io transaction 0xce1f…7ccb etherscan.io
- address HarryPotterObamaJustinHuobiSun (USDT) verified contract source, USDT.sol and ERC20.sol, Etherscan etherscan.io
- address etherscan.io address 0x4Fe2…451d etherscan.io
- address etherscan.io address 0xC49a…7368 etherscan.io
- address etherscan.io address 0xD100…B030 etherscan.io
- analysis Sourcify match for HarryPotterObamaJustinHuobiSun contract on Ethereum repo.sourcify.dev
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.