
Date: August 9, 2026, with SlowMist's alert following on August 10
Chain: Ethereum
Loss: 70.83 ETH, about $136K
Class: redemption pricing that pays more when one trade is split into many
Above one line of arithmetic in USM's ethFromDefund() function sits a comment from the protocol's author. It explains that taking a geometric average of two prices at that step is "dicey", because it could produce "the highly perverse result that a larger fumIn returns strictly less ETH", and it concludes that "what alternative to geometric average gives the best results here is a complicated problem, but one simple option" is to take the arithmetic average instead.
The comment went into the USM repository in March 2021. About five years later, on August 9, 2026, an attacker redeemed one large position in 64 pieces instead of one and walked away with 70.83 ETH, about $136,000. The arithmetic average fixed the anomaly the comment worried about and created its mirror image: a payout that's larger when you split a redemption than when you make it in one go.
USM, published on GitHub under the usmfum organization, is an ETH-backed stablecoin designed to be ownerless and immutable. It has two tokens and four operations. USM is the stablecoin, and FUM is a second token whose holders absorb ETH price swings in return for upside. mint() turns ETH into USM, burn() turns USM back into ETH, fund() turns ETH into FUM, and defund() turns FUM back into ETH.
Each operation has to price a trade whose size moves the price. A big defund() drains ETH from the pool, which lowers the FUM price as the trade goes, so the contract needs one average price that represents the whole trade. Three of the four flows integrate the price path properly, with geometric means in usmFromMint() and fumFromFund() and an exponential integral in ethFromBurn(). ethFromDefund() alone takes the starting sell price and an estimated ending sell price and averages the two.
Because USM is ownerless and immutable, there's no admin key that can pause it or swap out a formula once it's deployed.
The ending price in ethFromDefund() is estimated against the pool and FUM supply as they stand before the trade, so the bigger the redemption, the lower that ending estimate falls and the lower the average price per FUM. After each redemption the contract updates its state, but per SlowMist's alert that contraction doesn't fully make up the difference, and integer rounding adds a little more in the redeemer's favor. The result is that 64 small defund() calls pay out strictly more ETH than one defund() of the same total size.
The property USM's pricing lacked is called split invariance, and it's the teaching moment here: in any pricing function where the caller picks the trade size, doing the trade in one call or in N calls should never pay more the second way. A supermarket that charged less per item when you went through the checkout once per item would be making the same mistake, and anyone with patience could profit from it.
The comment above the averaging step shows the author was guarding against the opposite failure, where large redeemers get punished. The arithmetic mean solved that and gave split redeemers the advantage instead.
The DeFiHackLabs reproduction replays the attack from block 25716149, one block before the exploit transaction 0xfae5...050e landed in block 25716150. The attacker contract at 0x5a5e...7de7 took a zero-fee flash loan of about 11,579.98 WETH from Morpho Blue, unwrapped it to ETH, and called fund() once with the full amount, minting a large FUM position.
It then called defund() 64 times, 63 times with fixed slices of about 971,629.67 FUM and a final time with whatever remained. Each slice priced its redemption against a smaller trade than the whole, and each one collected a little more ETH than its share of a single large redemption would have. The contract rewrapped the principal, repaid Morpho in the same transaction, and kept about 70.83 ETH. DeFiHackLabs notes that USM's oracle reads didn't change during the attack, so the profit came from the pricing and rounding alone, with no price manipulation involved.
SlowMist recorded the loss as 70.83 ETH, or about $136,000. A figure of $212,490 also circulated, which appears to be the same 70.83 ETH priced at a different ETH rate. Alerts that name usmfum refer to this same exploit, since usmfum is the GitHub organization that publishes USM.
Olympix reads a contract the way an attacker does, as states, transitions and entry points, and it takes in the context around the code, including comments and design notes, to infer the invariants a system relies on but never writes down. USM's are unusually easy to read off its own source, because its author spent a paragraph reasoning about how the defund() payout should behave as fumIn grows.
A contract's state space looks infinite, but it isn't. The engine maps the paths through it and uses signals to decide which are worth attacking. The signal in USM is a payout computed from a two-point average of prices on a path where the caller chooses the trade size and can repeat the call as often as they like, sitting next to sibling functions that integrate the same kind of price path differently. That narrows the search from every path to the feasible ones, and then to the sequences of fund() and defund() calls that can touch the pool's ETH.
For USM, the invariant follows from the same worry the comment raises: redeeming a given amount of FUM in one defund() call must pay out at least as much ETH as redeeming the same amount across any number of smaller calls. BugPoCer takes that promise about the whole system as its starting point and uses Counterexample-Guided Invariant Refutation to execute fund() and defund() sequences with different sizes and splits until one pays out more than the single call does. The engine's other method, Bounded Adversarial Verification in BugScout, runs the opposite direction, starting from a flagged spot in the code and trying known attack strategies against it, and both share the engine's symbolic execution. A detector that matches code patterns can't see this flaw, because the bug lives in what a sequence of correct-looking calls adds up to.
The counterexample BugPoCer returns is the August 9 exploit in miniature: a fund() call, the same FUM redeemed once and then in N slices, and the difference in ETH out, along with the minimal conditions that trigger it and a generated Forge test that replays it. Olympix runs this automatically in development, at every merge and before release, which for an immutable contract means the counterexample arrives while the formula can still be changed.
USM's author named the open question in the code and picked a reasonable answer under real uncertainty. Questions like that one are exactly what an invariant-driven engine can settle, by running the trade both ways and checking which pays more, before the contract becomes permanent. If your protocol prices trades whose size moves the price, book a technical walkthrough.
Protocol: USM (usmfum)
Chain: Ethereum
Date: August 9, 2026, 08:12:47 UTC (block 25716150)
Loss: 70.83 ETH (about $136,000, per SlowMist)
Attacker EOA: 0xb92b...50Fc
Attack contract: 0x5a5e...7de7
Vulnerable contract (USM): 0x2a7F...1675
FUM token: 0x8672...eDF3
Flash loan source (Morpho Blue): 0xBBBB...FFCb
Exploit tx: 0xfae5...050e
Block: 25716150
DeFiHackLabs: USM_exp.sol proof of concept