Reproduced Exploit
Burve: `NoopVault` donation attack drains a Closure's assets
Chain
Other
Category
untagged
Date
Jan 1970
Source
AuditVault
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. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 387. Standalone Foundry PoC and full write-up: 56956-burve-noopvault-donation-attack-drains-assets-from-a-closure_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/theft · vuln/erc4626-donation · mev/frontrun
Reproduction: a faithful minimal reproduction of the vulnerable finding — the bare
NoopVaultvertex vault is reproduced verbatim (marked@>) with faithful minimalERC20/ERC4626doubles that mirror OpenZeppelin's exact virtual-shares accounting; local deploy, no fork.
Root cause#
NoopVault is a bare OpenZeppelin ERC4626 vertex vault with no donation protection — it seeds no initial share supply and adds no meaningful virtual-share offset (_decimalsOffset defaults to 0). An attacker can front-run the vault's first use, mint the only share for 1 wei, then donate assets directly to inflate the share price, so the first honest Closure deposit mints zero shares and is lost. The vulnerable contract, reproduced verbatim:
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import "openzeppelin-contracts/token/ERC20/extensions/ERC4626.sol";
import "openzeppelin-contracts/token/ERC20/ERC20.sol";
@> contract NoopVault is ERC4626 {
constructor(
ERC20 asset,
string memory name,
string memory symbol
) ERC20(name, symbol) ERC4626(asset) {}
}
With no seeded supply and only the default +1 virtual asset / 1 virtual share, the share-to-asset ratio is fully attacker-controllable on the first deposit.
Why it's exploitable here#
Following the finding's donation attack with the reproduction's concrete values:
- The attacker front-runs the vault's first use:
deposit(1 wei)mints the only share, sototalSupply = 1andtotalAssets = 1. - The attacker donates
2e18of the asset by a raw ERC20transfer— no shares are minted, sototalAssetsjumps to2e18 + 1whiletotalSupplystays1. - The honest Closure deposits
1e18.previewDepositcomputesfloor(1e18 × (1 + 1) / (2e18 + 1 + 1)) = floor(2e18 / (2e18 + 2)) = 0shares. - The
1e18still transfers into the vault, but the Closure holds0shares and redeems0— a full1e18loss. Repeated across every vertex, this drains the entire Closure.
Attack path#
Marked-line walkthrough (Playground)#
The EVM Playground pins each step to the exact executed source line in 0x671d353a…:
- L50 — Approve the vault to pull: Setup: the ERC20 approve records an unlimited allowance so the vault can pull the caller's deposit — used for the attacker's 1-wei front-run.
- L84 — Deploy the drained asset token: Setup: the Exploit deploys the mintable BGT asset the attacker will front-run, donate into, and ultimately drain from the vault.
- L115 — Share conversion multiplies then divides: The vault's mulDiv forms assets × totalSupply, then divides by the donation-inflated totalAssets+1 — the math that floors the deposit to zero shares.
- L121 — Convert deposit into vault shares: previewDeposit routes through _convertToShares, valuing the 1e18 deposit against a totalAssets the attacker already inflated by a 2e18 donation.
- L137 — Honest deposit mints zero shares: The honest 1e18 deposit computes its shares BEFORE the asset transfers in, so the inflated price yields zero shares while the assets still move.
- L171 — Closure deposits into unguarded vault: Root cause: the honest Closure deposit enters the bare ERC4626 NoopVault (@> L159) with no virtual-share/donation guard, so its 1e18 mints zero shares.
PoC#
Registry (Foundry, local deploy — verbatim vulnerable source + harm-asserting test):
cd 56956-burve-noopvault-donation-attack-drains-assets-from-a-closure_exp && forge test -vvv
The browser Playground replays the same synthetic opcode-for-opcode and measures the harm: the attacker front-runs with 1 wei, donates 2e18, and the honest 1e18 Closure deposit mints zero shares and redeems nothing. Both gates are green (registry forge test PASS + Playground _verify-poc VERDICT: PASS).
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 56956-burve-noopvault-donation-attack-drains-assets-from-a-closure_exp (evm-hack-registry mirror).
- AuditVault finding: 387.
- Upstream DeFiHackLabs PoC directory: src/test.
Alerts & third-party analyses
- DeFiHackLabs incident explorer: search "Burve:
NoopVaultdonation attack drains a Closure's assets". - Web3Sec X hacked database: search.
- Rekt leaderboard: search.
- Solodit incident search: search.
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.