All incidents

Uwerx Pool Drain

Share
Aug 02, 2023 08:44 UTCAttackLoss: 174.78 ETHPending manual check1 exploit txWindow: Atomic
Estimated Impact
174.78 ETH
Label
Attack
Exploit Tx
1
Addresses
1
Attack Window
Atomic
Aug 02, 2023 08:44 UTC → Aug 02, 2023 08:44 UTC

Exploit Transactions

TX 1Ethereum
0x3b19e152943f31fe0830b67315ddc89be9a066dc89174256e17bc8c2d35b5af8
Aug 02, 2023 08:44 UTCExplorer

Victim Addresses

0x4306b12f8e824ce1fa9604bbd88f2ad4f0fe3c54Ethereum

Loss Breakdown

174.78ETH

Similar Incidents

Root Cause Analysis

Uwerx Pool Drain

1. Incident Overview TL;DR

On 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.

2. Key Background

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.

3. Vulnerability Analysis & Root Cause Summary

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.

4. Detailed Root Cause Analysis

The exploit sequence follows directly from the code and trace:

  1. The attacker helper contract borrowed 20000000000000000000000 WETH from the Balancer vault with zero fee.
  2. It called sync() on the WERX/WETH pair so reserves matched the live balances before manipulation.
  3. It spent the flash-loaned WETH to buy WERX from the pair, receiving 5053637872806935777652180 WERX according to the trace.
  4. It transferred 4429817738575912760684500 WERX back to the pair as deliberate excess inventory.
  5. It then called 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.
  6. Since 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.
  7. The trace records the pair's WERX balance dropping to exactly 100; the next sync() stored that manipulated reserve.
  8. The helper then sold the retained 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"
}

5. Adversary Flow Analysis

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:

  • Flash-loan funding: Balancer vault 0xba12222222228d8ba445958a75a0704d566bf2c8 lent 20,000 WETH with fee 0.
  • Reserve distortion: the helper bought WERX, transferred excess WERX into the pair, executed skim(address(1)), and called sync() to lock in a 100-WERX reserve.
  • Drain and settlement: the helper sold retained WERX into the manipulated pool, repaid the Balancer flash loan principal, withdrew the remaining WETH to ETH, and forwarded the ETH to the attacker EOA.

This entire lifecycle was realized in the single seed transaction 0x3b19e152943f31fe0830b67315ddc89be9a066dc89174256e17bc8c2d35b5af8.

6. Impact & Losses

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:

  • Uwerx token 0x4306b12f8e824ce1fa9604bbd88f2ad4f0fe3c54
  • WERX/WETH pair 0xa41529982bcccdfa1105c6f08024df787ca758c4

Loss summary:

  • ETH: "174780439904115214249" raw wei, 18 decimals

7. References

  • Seed transaction: 0x3b19e152943f31fe0830b67315ddc89be9a066dc89174256e17bc8c2d35b5af8
  • Attacker EOA: 0x6057a831d43c395198a10cf2d7d6d6a063b1fce4
  • Helper contract: 0xda2ccfc4557ba55eada3cbebd0aeffcf97fc14ca
  • Uwerx token: 0x4306b12f8e824ce1fa9604bbd88f2ad4f0fe3c54
  • WERX/WETH pair: 0xa41529982bcccdfa1105c6f08024df787ca758c4
  • Balancer vault: 0xba12222222228d8ba445958a75a0704d566bf2c8
  • Evidence used: transaction metadata, verbose execution trace, balance diff, and verified Uwerx source collected under the session artifacts