0x5e151627dc06ec4f2db5be2f48248f320ad3450aba42b1bbd00131bcbaa4f0ae0x8cc0c46000a6a4097f9c62293ce62ee5b81e6dfdEthereumA swap on the WETH/VRUG UniswapV2Pair at block 21138660 drained 2.903872687851807969 WETH by exploiting reserve/balance drift in the pair contract. The transaction used data=0x, so no callback repayment was required. The swap consumed preloaded VRUG surplus as implied input (amount1In) and extracted WETH to an arbitrary recipient, which is reproducible by any actor that finds the same on-chain state conditions.
0x8CC0c46000A6a4097F9C62293CE62Ee5B81E6dFD (WETH/VRUG pool).0xC02a...CC2, Token1 is VRUG at 0xC273...88D9.amount0In or amount1In is positive.data = 0x), no external repayment is required for this swap call.The root cause is a mismatch between Uniswap V2’s stored reserves and actual token balances at swap time. If a pool has balance1 > reserve1 before execution, then amount1In can be computed as this surplus, even when no token1 transfer occurred in the exploit transaction. The swap path treats that synthetic-in-transaction increase as valid input because of the pair’s invariant check structure.
In this incident, the attacker-called transaction set amount0Out > 0, amount1Out = 0, and data = 0x. The contract observed sufficient implied input from pre-existing VRUG drift, passed its invariant checks, and delivered 2903872687851807969 wei WETH out. This is a violation of canonical swap settlement assumptions: input should be attributable to same-transaction causality, not stale balance drift.
The pair implementation computes:
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
Thus, when balance1 already exceeds _reserve1, a positive amount1In is inferred without a transfer in the same tx.
Observed exploit tx trace for this incident shows:
UniswapV2Pair::swap(2903872687851807969, 0, 0x0000E0Ca771e21bD00057F54A68C30D400000000, 0x)
Swap(sender: 0x0800869..., amount1In: 850000000000000000000000000, amount0Out: 2903872687851807969, amount1Out: 0)
and liquidity reconciliation updates:
Sync(reserve0: 304450735650539443, reserve1: 938849203688029037326840863)
Pre-state pool snapshot (from collected artifacts) indicates:
reserve1 before swap: 88849203688029037326840863938849203688029037326840863That is a pre-existing surplus of 850000000000000000000000000 VRUG, which became the inferred amount1In used to satisfy input requirements.
pair.swap with amount0Out = 2.903872687851807969 WETH, amount1Out = 0, recipient arbitrary, data = 0x._reserve1, amount1In is non-zero without VRUG being sent in this tx.This flow uses only public methods/values and is permissionless.
Measured state delta for this tx:
32083234235023474123044507356505394432903872687851807969 wei (2.903872687851807969 WETH)VRUG balance at the pair is unchanged for the tx (delta = 0), confirming the exploitable input came from balance drift rather than fresh transfer.
0x5e151627dc06ec4f2db5be2f48248f320ad3450aba42b1bbd00131bcbaa4f0aeswap and Sync eventsswap accounting logic and reserve/balance checks