Calculated from recorded token losses using historical USD prices at the incident time.
0x3b19e152943f31fe0830b67315ddc89be9a066dc89174256e17bc8c2d35b5af80x4306b12f8e824ce1fa9604bbd88f2ad4f0fe3c54EthereumOn Ethereum mainnet block 17826203, transaction 0x3b19e152943f31fe0830b67315ddc89be9a066dc89174256e17bc8c2d35b5af8 drained the public WERX/WETH Uniswap V2 pool at 0xa41529982bcccdfa1105c6f08024df787ca758c4. The attacker used a helper contract plus a zero-fee Balancer flash loan, bought WERX, collapsed the pair's WERX reserve to 100, then sold retained WERX back into the broken pool and realized 174780439904115214249 wei of net ETH profit.
The root cause was in the verified Uwerx token at 0x4306b12f8e824ce1fa9604bbd88f2ad4f0fe3c54. The token initialized both marketingWalletAddress and uniswapPoolAddress to address(1), and its special transfer branch credited the full amount to the recipient before burning 1% from the sender. When the public pair executed skim(address(1)), the pair itself became the token sender, so the buggy transfer path burned the pair's balance and enabled a permissionless reserve-collapse sequence.
Two public Uniswap V2 maintenance functions are central here:
skim(to) transfers token balances above stored reserves to to.sync() updates stored reserves to match current token balances.The Uwerx token applies custom fee logic when to == uniswapPoolAddress. Because uniswapPoolAddress remained address(1) from deployment, any transfer to address(1) was treated as a pool-directed transfer even though address(1) is not the pool.
The attack surface was therefore fully public: a user only needed access to the live pool, the public Balancer vault, and ordinary transaction submission.
The vulnerable logic is in the verified Uwerx ERC20 implementation. The contract initializes marketingWalletAddress and uniswapPoolAddress to address(1). Its _transfer function first subtracts amount from from and adds the full amount to to. If to == uniswapPoolAddress, it then emits reduced transfer events, emits a marketing transfer, and burns burnAmount from from. That means the actual balance updates no longer match the intended pool-fee accounting.
The relevant code path is:
address private marketingWalletAddress = 0x0000000000000000000000000000000000000001;
address private uniswapPoolAddress = 0x0000000000000000000000000000000000000001;
unchecked {
_balances[from] = fromBalance - amount;
_balances[to] += amount;
}
if (to == uniswapPoolAddress) {
uint256 userTransferAmount = (amount * 97) / 100;
uint256 marketingAmount = (amount * 2) / 100;
uint256 burnAmount = amount - userTransferAmount - marketingAmount;
emit Transfer(from, to, userTransferAmount);
emit Transfer(from, marketingWalletAddress, marketingAmount);
_burn(from, burnAmount);
}
The broken invariant is: when special pool-fee logic applies, recipient credit plus all side allocations must exactly match the sender debit, and only the real pool address must trigger that branch. Uwerx violates both requirements.
The exploit sequence follows directly from the code and trace:
20000000000000000000000 WETH from the Balancer vault with zero fee.sync() on the WERX/WETH pair so reserves matched the live balances before manipulation.5053637872806935777652180 WERX according to the trace.4429817738575912760684500 WERX back to the pair as deliberate excess inventory.skim(address(1)). Because skim transfers token excess from the pair to address(1), the pair became from in Uwerx _transfer and address(1) became to.to == uniswapPoolAddress == address(1), Uwerx entered the buggy branch and burned 44298177385759127606845 WERX from the pair after already crediting address(1) with the full skim amount.100; the next sync() stored that manipulated reserve.623820134231023016967680 WERX into a pool that still held 20174786100489116297837 WETH, receiving 20174786100489116297833 WETH and leaving the pair with only 4 wei of WETH.The decisive on-chain evidence appears in the execution trace:
0xa4152998...::skim(0x0000000000000000000000000000000000000001)
0x4306b12f...::transfer(0x0000000000000000000000000000000000000001, 4429817738575912760684500)
emit Transfer(src: pair, dst: 0x000...001, wad: 4296923206418635377863965)
emit Transfer(src: pair, dst: 0x991C13B8..., wad: 88596354771518255213690)
emit Transfer(src: pair, dst: 0x000...000, wad: 44298177385759127606845)
storage pair_balance: ... -> 100
The balance-diff artifact independently confirms the semantic end state:
{
"pair_werx_after": "623820134231023016967780",
"address1_werx_after": "4429817738575912760684500",
"attacker_profit_wei": "174780439904115214249"
}
The adversary cluster contains the EOA 0x6057a831d43c395198a10cf2d7d6d6a063b1fce4 and helper contract 0xda2ccfc4557ba55eada3cbebd0aeffcf97fc14ca. The EOA submitted the seed transaction and received the final ETH profit. The helper contract executed the flash loan, pool manipulation, and settlement.
The on-chain flow was:
0xba12222222228d8ba445958a75a0704d566bf2c8 lent 20,000 WETH with fee 0.skim(address(1)), and called sync() to lock in a 100-WERX reserve.This entire lifecycle was realized in the single seed transaction 0x3b19e152943f31fe0830b67315ddc89be9a066dc89174256e17bc8c2d35b5af8.
The exploit drained the WERX/WETH pair's counter-asset reserve. After execution, the pair held only 4 wei of WETH and 623820134231023016967780 WERX. The attacker realized 174780439904115214249 wei of net ETH profit, as shown by the seed balance diff for the attacking EOA.
Affected component:
0x4306b12f8e824ce1fa9604bbd88f2ad4f0fe3c540xa41529982bcccdfa1105c6f08024df787ca758c4Loss summary:
ETH: "174780439904115214249" raw wei, 18 decimals0x3b19e152943f31fe0830b67315ddc89be9a066dc89174256e17bc8c2d35b5af80x6057a831d43c395198a10cf2d7d6d6a063b1fce40xda2ccfc4557ba55eada3cbebd0aeffcf97fc14ca0x4306b12f8e824ce1fa9604bbd88f2ad4f0fe3c540xa41529982bcccdfa1105c6f08024df787ca758c40xba12222222228d8ba445958a75a0704d566bf2c8