
Date: June 29, 2026
Chain: Base and Arbitrum
Loss: about 33.78 WETH, roughly $53K
Class: redeem pays the WETH leg twice, once in share value and once in kind
A little before 07:00 UTC on June 29, 2026, the team behind a set of Uniswap V3 yield vaults sent an unusual transaction on Base and Arbitrum. It moved no money. Its input data carried a message to one address: the team said it was operating the affected vaults, that the address and method were fully traced, and that the attacker could keep 10% as a bounty if the remaining 90%, about 30 WETH, came back within 24 hours. Within fifteen minutes, the attacker moved 23.08 WETH on Base and 10.70 WETH on Arbitrum to another address instead.
Those 33.78 WETH, about $53,000, came out of three deployments of a custom ERC-4626 vault named Vault4626 over roughly two and a half hours. The vault's redeem() function paid every redeemer the value of their shares, a value that already counted the vault's WETH, and then also handed over the WETH itself.
Vault4626 takes deposits in USDC and mints ERC-4626 shares, the standard token that records each depositor's slice of a vault. Behind it, a strategy contract runs a USDC/WETH liquidity position on Uniswap V3, so at any moment the vault's value sits in three places: USDC held idle, the USDC and WETH inside the Uniswap position, and any loose WETH sitting in the vault itself. The asset is USDC, and WETH is what the code calls the non-asset leg.
The verified Vault4626 source on BaseScan shows how totalAssets() adds those pieces up. It takes idle USDC, adds the USDC in the position, converts the position's WETH into USDC at a pool tick, and does the same for the vault's own WETH balance, returning idle plus assetInLP plus quoted plus vaultNonAssetQuoted. When the vault's twapWindowSeconds setting is zero, that tick is the pool's live spot tick from slot0, and according to the DeFiHackLabs reproduction, that's how the exploited vault was configured. Shares are worth totalAssets() divided by the share supply, so every WETH the vault holds raises the USDC value of every share.
redeem() starts the way any ERC-4626 vault does. It calls convertToAssets(shares) to work out how much USDC the redeemer is owed, pulls liquidity out of the strategy if the vault doesn't have enough idle USDC, and pays that amount. Then a later step sends WETH. When the redemption runs down the fallback branch, the code works out how much of the vault's available WETH came from the withdrawn liquidity and transfers it to the redeemer as the non-asset payout, on top of the USDC.
Those two payments overlap. The USDC figure already includes the WETH, priced in, because totalAssets() counted it, and the WETH payout hands the same WETH over in kind. Defimon's alert describes the bug in exactly those terms: totalAssets() already values the WETH leg in USDC, and step 6 of redeem() also transfers the actual WETH.
A vault can count a holding as part of a share's value or pay it out separately alongside the share, but not both, because each unit of value has to leave through exactly one door. It's like paying a restaurant bill whose total already includes the tip and then leaving the tip on the table again. On an honest withdrawal the overlap is small enough to miss, but anyone who can inflate the WETH leg at will can make it as large as they like.
The attacker's address, 0xA27e...9bFA, started on Arbitrum at 02:50:04 UTC and collected 10.56 WETH in its first transaction there, followed by two smaller ones. The Base transaction that Defimon flagged, at 04:14:57 UTC, shows the method most clearly, and every step below comes from its token transfers.
The attack contract borrowed 1,755,018.73 USDC from Morpho and 12.92 WETH from Balancer, both as flash loans, money that has to be returned before the transaction ends. It deposited the full 1.755M USDC into the vault at 0x72db...2020, which, according to the DeFiHackLabs proof of concept, held only about 21,945 USDC, so the attacker now owned roughly 98.8% of all shares. It then sent the 12.92 WETH straight to the vault as a donation. Nothing about the donation created shares, but totalAssets() counted it immediately, so the USDC value of every share went up.
Then it redeemed everything. The vault pulled its position out of Uniswap, which brought in 16,840.11 USDC and 3.06 WETH, and paid the attacker 1,771,858.84 USDC, 16,840.11 more than the deposit, plus 15.83 WETH, which covered the donated 12.92 and the WETH from the position. The attacker swapped the extra 16,840.11 USDC for 10.62 WETH on Uniswap, repaid Balancer's 12.92 WETH and Morpho's 1.755M USDC, and sent 13.53 WETH to the attacker's address. The donation came back in full, and the vault's entire Uniswap position went with it.
Four more Base transactions followed against other Vault4626 deployments, between 05:11 and 05:34 UTC, adding about 4.71 WETH and a further 0.0127 cbBTC, while an earlier Base transaction at 03:52 UTC had already brought in 4.84 WETH. Defimon counts three vaults hit across the two chains, for about 34 WETH in total.
The team's on-chain message went out at 06:48 UTC from 0xc7d5...355f, the address that deployed the Vault4626 contracts and the one it asked the funds to be returned to. At 07:01 and 07:02 UTC, the attacker sent the Arbitrum and Base proceeds to 0xA1f0...0D58 rather than to the team, and on July 2 the Arbitrum WETH was unwrapped and the attacker's address made bridge transactions on both chains. The Base vault's proxy now points to a different implementation than the one exploited, and the DeFiHackLabs proof of concept points to a post-incident patch and notes that the attack fails against a vault that caps the non-asset payout or blocks a deposit and redemption in the same block.
The attacker's address was itself an EIP-7702 account, an ordinary wallet that has delegated its behavior to a smart-contract implementation, which let it run the deployments and transfers above from a single address on both chains.
Olympix reads a vault the way an attacker does, as a set of states, transitions and entry points, and it takes in the context around the code, here the ERC-4626 standard and the vault's own comments about pricing the non-asset leg, to infer the invariants the vault depends on. In Vault4626, the state that matters is total value against total shares, and the entry points that touch it are deposit(), redeem() and plain token transfers into the vault, which anyone can make.
Olympix's static analysis surfaces the signal in redeem(): a single function that pays out a share's full value as computed by totalAssets(), which includes the vault's WETH, and then transfers WETH drawn from the same balance in a separate step. Olympix has flagged this class before in other codebases. From that signal, the engine narrows from every path through the vault, to the feasible ones that end in a redemption, to the ones where the vault holds non-asset tokens at redemption time, which is where the invariant lives: a redeemer should never receive more than their shares' pro-rata slice of everything the vault holds.
BugPoCer goes after that rule directly. Running Counterexample-Guided Invariant Refutation, it works outward from the vault, treats a direct WETH transfer as one of the moves an outsider can make, and executes deposit, donation and redemption sequences until one pays out more than the redeemer's slice. Symbolic execution supports the search by solving for the deposit size and donation that maximize the overpayment.
The counterexample it returns is the June 29 sequence: a deposit large enough to own almost every share, a WETH transfer straight to the vault, and a full redemption that pays the share value in USDC and the same WETH again. It states the minimal conditions, a vault holding any WETH and a redeemer who controls most of the shares, and it comes with a generated Forge test that replays the steps and asserts the redeemer left with more than their slice. Because the engine runs in development, at every merge and before release, it would have surfaced at the change that started counting the vault's own WETH inside totalAssets(), with the redemption path that already paid WETH out separately.
Vault4626's pricing code and its payout code were each reasonable on their own, and the loss came from where they met. Any vault that holds a second token should be able to say, for each unit of value, which single path it leaves by, and test that against someone who can donate tokens and redeem in the same transaction. To see how Olympix traces every unit of value through your vault's mint and redeem paths and hands back a runnable counterexample where one leaves twice, book a technical walkthrough.
Protocol: Vault4626 (custom ERC-4626 Uniswap V3 LP vault)
Chains: Base and Arbitrum
Date: June 29, 2026, first exploit tx 02:50:04 UTC (Arbitrum)
Loss: about 33.78 WETH plus 0.0127 cbBTC (about $53,000)
Attacker: 0xA27e...9bFA
Attack contract (Base main tx): 0x47e7...af3c
Vault4626 proxy (Base): 0x72db...2020
Vault4626 implementation (exploited): 0xd085...6a4d
Uniswap V3 WETH/USDC pool (Base): 0x6c56...1372
Team address (deployer, bounty offer): 0xc7d5...355f
First exploit tx (Arbitrum): 0x0bc9...9982
Main exploit tx (Base): 0x2f2e...d790
Earlier Base exploit tx: 0x1149...ceb4
Later Base exploit tx: 0x5662...6301
Base proceeds moved (23.08 WETH): 0x3dd4...a61a
Arbitrum proceeds moved (10.70 WETH): 0x438c...8f8f
Receiving address: 0xA1f0...0D58
Defimon Alerts: Vault4626 loss of ~34 WETH on Base and Arbitrum