All incidents

VINU Reserve Drain

Share
Jun 06, 2023 11:06 UTCAttackLoss: 3.26 WETHPending manual check1 exploit txWindow: Atomic
Estimated Impact
3.26 WETH
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Jun 06, 2023 11:06 UTC → Jun 06, 2023 11:06 UTC

Exploit Transactions

TX 1Ethereum
0xaf46a42fe1ed7193b25c523723dc047c7500e50a00ecb7bbb822d665adb3e1f3
Jun 06, 2023 11:06 UTCExplorer

Victim Addresses

0xf7ef0d57277ad6c2babf87ab64ba61abdd2590d2Ethereum
0xa8af8ac7acd97095c0d73ed51e30564d52b19cd8Ethereum

Loss Breakdown

3.26WETH

Similar Incidents

Root Cause Analysis

VINU Reserve Drain

1. Incident Overview TL;DR

In Ethereum mainnet block 17421007, transaction 0xaf46a42fe1ed7193b25c523723dc047c7500e50a00ecb7bbb822d665adb3e1f3 executed a single-transaction attack against the VINU token and its VINU/WETH Uniswap V2 pool at 0xa8af8ac7acd97095c0d73ed51e30564d52b19cd8. The attacker EOA 0x9748c8540a5f752ba747f1203ac13dae789033de funded helper contract 0xf73b8ea8838cba9148fb182e267a000f7cfba8dd, bought VINU with 0.1 ETH, repeatedly called VINU's public addLiquidityETH with the pair supplied as devaddr, forced the pair to sync to the reduced VINU balance, and then sold the acquired VINU back into the manipulated pool. The attacker extracted 3256513152378912968 wei of WETH and ended with a net cluster gain of 3140869820147531576 wei in ETH-equivalent after gas.

The root cause is a direct authorization and accounting failure in VINU.addLiquidityETH(address routeraddr,address lpraddr,address devaddr). Any caller can choose an arbitrary devaddr, including the live VINU/WETH pair, and the function immediately removes 80% of that address's VINU balance before any legitimate liquidity operation occurs. Because the caller also controls routeraddr, the attacker can route the call through a fake router that returns success without adding real liquidity, then exploit the distorted reserve state through a standard Uniswap V2 sync and swap flow.

2. Key Background

The affected token contract is VINU at 0xf7ef0d57277ad6c2babf87ab64ba61abdd2590d2. VINU is not a vanilla ERC20: its transfer path delegates balance handling to an external address stored in routerbyt, and it exposes a token-side addLiquidityETH helper that is callable by any external account. That helper is not the standard Uniswap router implementation; it directly mutates VINU balances and only then calls the user-supplied router.

The affected pool is the VINU/WETH pair at 0xa8af8ac7acd97095c0d73ed51e30564d52b19cd8, which public Etherscan source identifies as a verified UniswapV2Pair. In Uniswap V2, prices depend on cached reserves, and sync() updates those reserves to the pair's current token balances. That means any token contract that can arbitrarily lower the pair's token balance can alter the price seen by the next swap once sync() is called.

The relevant standard pair behavior is:

function sync() external lock {
    _update(
        IERC20(token0).balanceOf(address(this)),
        IERC20(token1).balanceOf(address(this)),
        reserve0,
        reserve1
    );
}

This matters because VINU's bug let the attacker push the pair's actual VINU balance far below the previously cached reserve while leaving WETH untouched. A later sync() therefore converted the manipulated balances into official reserves.

3. Vulnerability Analysis & Root Cause Summary

This incident is an ATTACK category bug, not an MEV-only opportunity without broken invariants. The violated invariant is straightforward: only a token holder or an approved spender should be able to reduce that holder's balance, and any liquidity helper should preserve the relationship between balances and actual liquidity additions. VINU breaks both rules in one public function. addLiquidityETH lets an arbitrary caller nominate any devaddr, subtracts 80% of that address's VINU balance, credits those tokens to the VINU contract itself, and only afterwards performs router interactions. Because routeraddr is caller-controlled, the attacker supplied a fake router contract that implemented the required methods and simply returned success. No real liquidity was added, but the pair still lost VINU balance. Once the attacker called sync(), the pair adopted the depleted VINU reserve and allowed an extremely favorable WETH-out swap against a tiny VINU-in trade.

4. Detailed Root Cause Analysis

The vulnerable VINU code is explicit:

function addLiquidityETH(address routeraddr,address lpraddr,address devaddr) external payable {
    uint256 senderBalance = _balances[devaddr] * 80 /100;
    _balances[devaddr] -= senderBalance;
    _balances[address(this)] = senderBalance;
    emit Transfer(devaddr, address(this), senderBalance);
    IUniswapV2Router02 router = IUniswapV2Router02(routeraddr);
    _approve(address(this), address(router), _totalSupply);
    address uniswapV2Pair = IUniswapV2Factory(router.factory()).createPair(address(this), router.WETH());
    router.addLiquidityETH{value: msg.value}(address(this),balanceOf(address(this)),0,0,lpraddr,block.timestamp);
    IERC20(uniswapV2Pair).approve(address(router), ~uint(0));
}

The code-level breakpoint is the first three state-changing lines. They execute before any trustworthy router validation and do not require devaddr to be msg.sender, owner-controlled, or approved in any way. That lets any caller confiscate 80% of the VINU balance of any chosen address.

The observed exploit used that exact behavior against the live VINU/WETH pair:

VINU::addLiquidityETH(fakeRouter, attackerHelper, VINU/WETH Pair)
  emit Transfer(src: VINU/WETH Pair, dst: VINU, wad: 373769865218439328)
VINU::addLiquidityETH(fakeRouter, attackerHelper, VINU/WETH Pair)
  emit Transfer(src: VINU/WETH Pair, dst: VINU, wad: 74753973043687865)
VINU::addLiquidityETH(fakeRouter, attackerHelper, VINU/WETH Pair)
  emit Transfer(src: VINU/WETH Pair, dst: VINU, wad: 14950794608737573)
VINU::addLiquidityETH(fakeRouter, attackerHelper, VINU/WETH Pair)
  emit Transfer(src: VINU/WETH Pair, dst: VINU, wad: 2990158921747515)
VINU/WETH Pair::sync()
  emit Sync(3431124883166006871, 747539730436879)
VINU/WETH Pair::swap(3256513152378912968, 0, attackerHelper, 0x)

The step-by-step mechanism is deterministic:

  1. The attacker helper first bought 13983585451343353 VINU with 0.1 ETH, leaving the pair with 3431124883166006871 WETH and 467212331523049160 VINU.
  2. The attacker called addLiquidityETH four times with devaddr = pair. Because each call removes 80% of the pair's current VINU balance, the pair-side VINU balance fell along this sequence: 467212331523049160 -> 93442466304609832 -> 18688493260921967 -> 3737698652184394 -> 747539730436879.
  3. The supplied fake router satisfied the expected interface but returned success without transferring any real liquidity into the genuine pair. The balance loss therefore remained uncompensated.
  4. The attacker called sync(), and the verified Uniswap V2 pair updated reserves to the manipulated live balances: 3431124883166006871 WETH and 747539730436879 VINU.
  5. The attacker transferred the previously purchased 13983585451343353 VINU back to the pair and used standard Uniswap pricing against the distorted reserves to compute and withdraw 3256513152378912968 WETH.

The balance-diff artifact confirms the outcome. The pair's VINU balance moved from 481195916974392513 before the transaction to 14731125181780232 after the full exploit, a net delta of -466464791792612281. The sender EOA lost 515643331231381392 wei of native ETH, while the helper contract finished with 400000004000000000 wei native ETH and the extracted WETH position.

5. Adversary Flow Analysis

The adversary flow was a compact four-stage sequence inside one attacker-crafted transaction:

  1. Funding and entry: The EOA 0x9748c8540a5f752ba747f1203ac13dae789033de sent 0.5 ETH to helper contract 0xf73b8ea8838cba9148fb182e267a000f7cfba8dd, making that helper the transaction target and execution environment.
  2. Initial VINU buy: The helper sent 0.1 ETH through the canonical Uniswap V2 router 0x7a250d5630b4cf539739df2c5dacb4c659f2488d and received 13983585451343353 VINU.
  3. Reserve manipulation: The helper repeatedly called VINU's public addLiquidityETH, each time passing the live pair as devaddr and an attacker-controlled fake router as routeraddr. The pair kept losing VINU while WETH stayed unchanged.
  4. Profit realization: After sync(), the helper transferred its bought VINU back into the pair and called swap to pull out 3256513152378912968 WETH.

The attacker-related accounts are defensibly identified:

  • 0x9748c8540a5f752ba747f1203ac13dae789033de: the EOA that submitted and funded the exploit.
  • 0xf73b8ea8838cba9148fb182e267a000f7cfba8dd: the helper contract that received VINU, performed the reserve-drain sequence, and ended with profit-bearing WETH and native ETH.

The exploit remained permissionless throughout. No privileged role, stolen key, private contract artifact, or non-public state was needed. The attacker needed only public Ethereum state, the verified VINU bytecode/source, a locally deployed fake router, and standard Uniswap V2 interactions.

6. Impact & Losses

The directly measurable loss was to the VINU/WETH pool, which lost 3256513152378912968 WETH (3.256513152378912968 WETH, 18 decimals) in a single transaction. The pool's WETH balance dropped from 3431124883166006871 to 174611730787093903, while its VINU reserve had already been forced down to a tiny remainder before the sell-back step.

From the attacker-cluster perspective, the net gain was 3140869820147531576 wei in ETH-equivalent after gas. That valuation combines the sender EOA's native ETH balance change, the helper contract's final native ETH balance, and the helper contract's final WETH balance. The result shows that the exploit was not merely reserve distortion; it produced realized, extractable profit in the reference asset.

7. References

  1. Exploit transaction: 0xaf46a42fe1ed7193b25c523723dc047c7500e50a00ecb7bbb822d665adb3e1f3 in Ethereum block 17421007.
  2. VINU token contract: 0xf7ef0d57277ad6c2babf87ab64ba61abdd2590d2.
  3. VINU/WETH Uniswap V2 pair: 0xa8af8ac7acd97095c0d73ed51e30564d52b19cd8.
  4. Canonical Uniswap V2 router used for the initial buy: 0x7a250d5630b4cf539739df2c5dacb4c659f2488d.
  5. Collected exploit trace and metadata: /workspace/session/artifacts/collector/seed/1/0xaf46a42fe1ed7193b25c523723dc047c7500e50a00ecb7bbb822d665adb3e1f3/trace.cast.log, /workspace/session/artifacts/collector/seed/1/0xaf46a42fe1ed7193b25c523723dc047c7500e50a00ecb7bbb822d665adb3e1f3/metadata.json, and /workspace/session/artifacts/collector/seed/1/0xaf46a42fe1ed7193b25c523723dc047c7500e50a00ecb7bbb822d665adb3e1f3/balance_diff.json.
  6. Verified VINU source: /workspace/session/artifacts/collector/seed/1/0xf7ef0d57277ad6c2babf87ab64ba61abdd2590d2/src/Contract.sol.
  7. Verified VINU/WETH pair source evidence from Etherscan: /workspace/session/artifacts/auditor/iter_1/pair_sourcecode_etherscan.json.