Summary
On 12 November 2020 the Akropolis Delphi savings product on Ethereum lost about 2,030,841 DAI, roughly $2 million, from its yCurve and sUSD pools. The SavingsModule deposit path did not validate that the supplied token was a registered pool token and had no reentrancy guard, while crediting deposits from the contract's token balance delta before and after the transfer. The attacker deployed a fake ERC-20 whose transferFrom contained a malicious hook; depositToProtocol() called IERC20(token).safeTransferFrom() on that attacker-controlled address, handing over control flow. The fake token re-entered deposit(), this time supplying real flash-loaned DAI and minting pool tokens for it; when control unwound to the outer deposit, the balance-delta calculation observed the same DAI increase and minted pool shares a second time, double-counting one real deposit. Funded by a dYdX flash loan, the attacker looped this across seventeen transactions, a reentrancy compounded by missing token-address validation.
How to avoid it in your code
- Whitelist and validate the deposit token against registered pool assets before any transfer
- Add a nonReentrant guard to deposit and every entrypoint that mints internal shares
- Do not compute credited amounts from balance-delta when the token contract is untrusted or caller-supplied
- Mint shares only after the transfer settles and finalize accounting before returning
- Treat any external transferFrom on an arbitrary token address as an untrusted callback
References
Related vulnerabilities
All Web3 →- CRITICALWEB3-ABRACADABRA-2025
In late March 2025 Abracadabra.Money lost about $13 million (roughly 6,260 ETH) on Arbitrum when an attacker abused the GMX V2 gmCauldrons that accept GMX GM liquidity tokens as collateral. GMX deposits are asynchronous, so the attacker submitted deposit orders with unsatisfiable minOut values that GMX rejected, returning the input USDC to the cauldron's order/router contract while the cauldron's accounting still counted that pending position as live collateral. Functions such as sendValueInCollateral removed real tokens during liquidation without clearing inputAmount/minOut state, so orderValueInCollateral kept reporting phantom collateral. Inside a single cook() batch the attacker borrowed MIM against this ghost collateral, self-liquidated to pull out the real returned tokens, and reborrowed, while the end-of-cook solvency check still read the stale inflated collateral value and passed. The accounting bypass let the attacker borrow against effectively non-existent collateral and extract MIM.
- CRITICALWEB3-PENPIE-2024
On September 3, 2024, Penpie, a yield protocol built on Pendle, was drained of about $27.3 million (11,113.6 ETH in wstETH, sUSDe, egETH and rswETH) across Ethereum and Arbitrum. The root cause was a cross-function reentrancy enabled by permissionless market registration: registerPenpiePool trusted any market from Pendle's PendleMarketFactoryV3 without validating the Standardized Yield (SY) token, so the attacker registered a fake market whose SY was their own contract. PendleStakingBaseUpg.batchHarvestMarketRewards (and its internal _harvestBatchMarketRewards) snapshotted reward-token balances before and after calling the market's redeemRewards, but lacked a nonReentrant guard. The malicious SY's claimRewards callback re-entered PendleStakingBaseUpg.depositMarket with flash-loaned Pendle LP tokens mid-accounting, so the deposit was misattributed as harvested rewards, inflating the attacker's reward balance. Although depositMarket itself carried a nonReentrant modifier, the two functions did not share a lock, so the unguarded harvest path let the attacker re-enter the guarded deposit path and claim the inflated rewards via MasterPenpie.multiclaim.
- HIGHWEB3-VOW-2024
On August 13, 2024 the Vow (Vowcurrency) protocol lost about $1.2 million (~452 ETH) when its own admin temporarily misconfigured a price setter and an MEV bot pounced. Vow's usdRateSetter admin key called setUSDRate and changed the VOW-to-vUSD exchange rate from 1 to 100 - the team later said it was testing the rate-setter while preparing a lending pool - then reverted it. The function had no input validation and no rate-change delay or timelock, and the inflated rate was readable on-chain for the window between the two transactions. An attacker-controlled MEV bot, its contract deployed 110 days earlier and funded via Tornado Cash, detected the change and within two blocks swapped VOW into vUSD at the 100x rate, minting roughly 148.7 million vUSD far above its backing, then dumped it for ETH and USDT on Uniswap. The VOW token fell 80-87%. The root cause was an unbounded, unprotected privileged setter exposed without a timelock, turning a careless admin action into instantly exploitable on-chain state.
- CRITICALWEB3-UWULEND-2024
On June 10, 2024, UwU Lend, an Aave-fork lending protocol on Ethereum, lost about $19.3 million, followed by a second ~$3.7 million drain on June 13, 2024 (combined ~$23 million). The root cause was flash-loan oracle manipulation of the sUSDe price feed: the custom sUSDePriceProviderBUniCatch oracle priced sUSDe as the median of 11 sources, 5 of which read instantaneous Curve pool spot prices via get_p (no TWAP/EMA smoothing) across the FRAXUSDe, USDeUSDC, USDeDAI, USDecrvUSD and GHOUSDe pools. Using a roughly $3.8 billion flash loan, the attacker swapped large USDe amounts to suppress the median sUSDe price, set up positions, then reversed the swaps to inflate it, rendering their own leveraged position liquidatable and self-liquidating repeatedly to harvest base assets at favorable rates. Curve explicitly advises against using get_p spot reads for oracles. The June 13 follow-up reused collateral left from the first attack, since sUSDe was not disabled as borrowable collateral.
- CRITICALWEB3-CURVE-VYPER-2023
On July 30, 2023 several Curve Finance native-ETH stable pools were exploited via a compiler/toolchain supply-chain bug in specific Vyper versions (0.2.15, 0.2.16, 0.3.0). The compiler's storage-slot allocator assigned every @nonreentrant(key) decorator its own unique storage slot instead of reusing one shared slot per key, so functions meant to share a single reentrancy lock each got an independent, separately-set lock. This left the guard effective against single-function reentrancy but defeated cross-function reentrancy, letting an attacker re-enter a different guarded function via the native-ETH transfer callback while balances were mid-update. WETH-paired pools were unaffected; the exploited native-ETH pools included CRV/ETH, pETH/ETH, msETH/ETH and alETH/ETH, impacting Alchemix, JPEG'd and Metronome. Gross losses were around $61M; white-hat actors and MEV bots such as c0ffeebabe.eth returned a significant portion, reducing net losses to roughly $52M.
- CRITICALWEB3-CONIC-2023
On 21 July 2023 Conic Finance's ETH Omnipool on Ethereum lost roughly 1,700 ETH, about $3.6 million, to a read-only reentrancy attack. The attacker flash-loaned around $134 million, deposited into the Curve rETH pool, then called Curve's remove_liquidity(), which sends ETH to the recipient before the pool's totalSupply and balances are finalized, triggering the attacker contract's fallback during an inconsistent intermediate state. Inside that callback the attacker re-entered ConicEthPool.withdraw(), causing Conic's Curve LP oracle to value the LP token from Curve's virtual price and totalSupply while the pool was mid-operation, returning an inflated price. Conic's reentrancy guard was bypassed because its _isETH check assumed Curve v2 ETH pools list the native ETH placeholder address (0xEeee...EEeE) as a coin, whereas they actually use the WETH address, so the guard never fired. The inflated valuation let the attacker mint excess cncETH and withdraw more than deposited.