
Four exploits in the second half of August, $292K lost across Arbitrum, Ethereum and BNB Chain. None involved a stolen key or a compromised admin. In every case the contract executed exactly as written — a signature was checked, a price was read, a redemption was calculated, a burn was settled — and the logic behind those steps was wrong in a way that only surfaces when someone goes looking for it. Below is what broke in each, and the invariant that would have caught it.
Two independent defects stacked, either of which alone would have capped the loss. The manager signature bound nothing: the signed digest carried no position ID, caller, position manager, nonce, deadline, or chain ID, so one legitimately-issued signature authorized an unbounded set of operations instead of exactly one. The attacker replayed that single signature across 21 Uniswap V3 LP positions and force-burned each via partialBurn. The burn was then priced off instantaneous pool state with no TWAP and no slippage bound, so a flash loan could skew the pool first and make every replayed burn settle in the attacker's favor. The first defect opened the door; the second made walking through it worth $30K.
Invariant broken: one signature authorizes exactly one operation, and an exit must not price off instantaneous pool state.
A redemption-pricing formula that isn't split-invariant. ethFromDefund() prices a FUM redemption as the arithmetic mean of two endpoint prices — the pre-trade sell price and an estimated post-trade sell price computed against the un-updated pool and supply. Because the payout rate per FUM falls as a single redemption grows while the per-redemption state contraction doesn't fully compensate, 64 small defund() calls pay out strictly more than one call of the same total size. The attacker used a flash loan to set up pool state through fund(), split the exit into 64 pieces, and kept the difference. Notably, USM's three other pricing flows integrate the price path correctly — two geometric means and one exponential integral. ethFromDefund alone used a flat two-point arithmetic average.
Invariant broken: splitting a trade must never pay better than executing it whole.
Spot-price manipulation feeding a mint calculation. FoxLpBondsPool.stake() computes stakeAmount = usdtAmount * 1e18 / swapPrice, where swapPrice comes from the FOX/USDT PancakeSwap spot price via getSwapPrice → router.getAmountsOut, guarded only by a caller-supplied _swapPrice band with no TWAP. The attacker chained flash loans to crush the FOX/USDT spot price, which collapsed swapPrice and inflated both stakeAmount and the 3% inviter reward enormously, minting roughly 91M FOX plus 89M stakedFOX via Treasury.lpBonds, then dumped them through the FOX/USDT and StableSwap pools for stablecoins before repaying every loan in the same transaction. Two defects stacked: a mint amount derived from a manipulable spot price, and a stale value trusted across the swap without being re-derived.
Invariant broken: a mint amount must not derive from a price an attacker can move in the same transaction.
An atomic liquidity sandwich on the vault's Uniswap V3 accounting. The vault's state-dependent mint and burn accounting had no same-transaction mint-burn protection and no state snapshot. The attacker minted vault shares against one pool composition, used a 1,800 WETH Morpho flash loan to manipulate the price and accrue fees within the same transaction, then burned the shares under the changed position state — so burn() collected the pending fees and distributed the corresponding liquidity, fees, and idle balances pro rata in the attacker's favor. The transaction shows the full add/remove/collect/swap loop through Uniswap V3 ENS pools with the flash loan opening and closing in one transaction. SlowMist is explicit that the core issue is the missing same-transaction mint-burn guard, not a standalone oracle manipulation or share over-mint.
Invariant broken: a position entered and exited in one transaction must not capture value accrued within it.