Reproduced Exploit

Vault4626 — Donation of Non-Asset WETH Inflates totalAssets() and Is Paid Out on redeem()

1. totalAssets() (Vault4626.sol:496-536) returns idle USDC + LP value plus TWAP-quoted vault balances of the non-asset token (WETH):

Jun 2026Basedefi3 min read

Loss

~13.53 WETH profit (18366634484619156667 − 4837426434749649311 = 13529208039869487356 wei)

Chain

Base

Category

defi

Date

Jun 2026

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. Step by Solidity line or by opcode across all call depths; the source line, Stack, Memory, Storage, Transient storage and Return value stay in sync. Click any opcode or source line to jump. No backend, no live RPC.

Loading fork state…

Source & credit. Exploit reproduction, trace data, and analysis adapted from DeFiHackLabs by SunWeb3Sec — an open registry of reproduced on-chain exploits. Standalone Foundry PoC and full write-up: 2026-06-Vault4626_exp in the evm-hack-registry mirror. Upstream DeFiHackLabs PoC: src/test/…/Vault4626_exp.sol.


Vulnerability classes: vuln/defi/donation-attack · vuln/logic/incorrect-accounting · vuln/defi/flash-loan-attack

Reproduction: offline Foundry project at this folder. Trace: output.txt. Verified source: src_Vault4626.sol (implementation) behind proxy 0x72dbAA8A….


Key info#

Loss~13.53 WETH profit (18366634484619156667 − 4837426434749649311 = 13529208039869487356 wei)
Vulnerable contractVault4626 impl — 0xD08579102fc28355C5839019b730Ce58F84E6a4d
Vault proxy0x72dbAA8A09d71D09c6De0de439968e1E7c122020
Asset / non-assetUSDC (0x8335…2913) / WETH (0x4200…0006)
Attacker0xA27eAE743Cd8C03E9b7c25ebF43DADbBC6Df9bFA
Attack tx0x2f2e12fbdf541c28f3667153e5338f73a313096338dc5ca592453566debcd790
Chain / blockBase / 47,958,574 / June 2026
Bug classtotalAssets() quotes donated idle non-asset WETH into USDC value, while redeem() also transfers that WETH to the redeemer — double-counting donation as both share price and physical payout

TL;DR#

  1. totalAssets() (Vault4626.sol:496-536) returns idle USDC + LP value plus TWAP-quoted vault balances of the non-asset token (WETH):

    SOLIDITY
    vaultNonAssetQuoted = getQuoteAtTick(tick, vaultNonAsset, nonAssetToken, asset);
    return idle + assetInLP + quoted + vaultNonAssetQuoted;
    
  2. redeem() (Vault4626.sol:390-490) sizes the USDC payout from convertToAssets(shares) (which uses inflated totalAssets()), then additionally transfers a pro-rata share of the vault's WETH to the redeemer.

  3. Attack: Morpho-flash USDC → deposit for almost all shares → Balancer-flash WETH and donate it to the vault → redeem inflated shares → receive both overpriced USDC and the donated WETH → swap surplus USDC to WETH → repay flash loans → keep ~13.53 WETH.

  4. Reproduced: attacker WETH 4.837 → 18.367 (output.txt); vault totalAssets drops sharply.


Vulnerable code#

SOLIDITY
// totalAssets — quotes donated WETH into share price
uint256 vaultNonAsset = IERC20(nonAssetToken).balanceOf(address(this));
uint256 vaultNonAssetQuoted = vaultNonAsset > 0
    ? VaultMathLib.getQuoteAtTick(tick, vaultNonAsset, nonAssetToken, address(asset)) : 0;
return idle + assetInLP + quoted + vaultNonAssetQuoted;

// redeem — pays USDC from convertToAssets (uses totalAssets) AND transfers WETH
assets = convertToAssets(shares);           // inflated by donation
...
_safeTransfer(nonAssetToken, receiver, nonAssetToSend);  // also pays the WETH

Attack flow#

sequenceDiagram participant A as Attacker participant M as Morpho participant B as Balancer participant V as Vault4626 A->>M: flashLoan USDC A->>B: flashLoan WETH A->>V: deposit(all USDC) → majority shares A->>V: transfer WETH (donation) Note over V: totalAssets inflated by TWAP(WETH) A->>V: redeem(shares) → USDC + WETH A->>A: swap surplus USDC→WETH, repay flashes

Recommendation#

  1. Do not count unsolicited non-asset balances in totalAssets() unless they are already reserved for a specific LP/fee accounting bucket that is not also transferred on redeem.
  2. Prefer: only count strategy-reported balances; ignore raw balanceOf(vault) for non-asset tokens that redeem will pay out.
  3. Donation-attack hardening: track accounting balances (not raw ERC20 balances) for share price.

References#


Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.