
Date: September 5, 2026
Chain: Ethereum, Arbitrum, Filecoin
Loss: about $181K across Ethereum, Arbitrum and Filecoin, per Secured Finance's postmortem
Class: unfilled orders settled as filled after the order book's tree lost track of them
At 22:43:11 UTC on September 5, 2026, a bot run by c0ffeebabe.eth placed a lending order on Secured Finance's Ethereum WBTC market for 0.00099998 WBTC at a unit price of 3 out of 10,000, a price at which nobody would ever take the other side. Nobody did, and a few calls later the protocol settled that order as filled anyway and credited the bot with about 3.33 WBTC of future value. By the end of the same transaction the bot had pulled 0.89899003 WBTC, roughly $72,000, out of the protocol and swapped it for 28.85 ETH.
That transaction was one of 14 withdrawals across Ethereum, Arbitrum and Filecoin that Secured Finance later traced, for a total impact of $180,856. Early alerts blamed a manipulated price, but the team's own postmortem and the event log of the transaction point somewhere else: a defect in how the order book removed filled price levels from its red-black tree could cut live, unfilled orders off from the tree, and the code that settled orders treated anything it could no longer reach as filled.
Secured Finance is a fixed-rate, fixed-term lending protocol that runs fully on-chain on Ethereum, Arbitrum and Filecoin. Lending on it means buying a zero-coupon bond at a discount, and borrowing means selling one, so the discount is the interest and it's locked in when the trade executes. A unit price of 9,500 means a lender pays 0.95 today for 1 at maturity. Lenders and borrowers meet on an on-chain order book with quarterly maturities, and deposits and collateral sit in the TokenVault.
Each side of each order book is stored in a red-black tree, a self-balancing structure where every node is a price level holding the orders at that price. When a market order eats through several price levels, the library removes them in bulk with dropLeft and dropRight instead of touching each order, which keeps gas predictable. Makers are settled lazily for the same reason. Rather than updating every maker when their order fills, the protocol works out later what each maker is owed from the order's amount and unit price, and it decides whether an order filled by checking whether that order can still be found in the tree.
Secured Finance's postmortem explains the defect. After a bulk removal, a simplified rebalancing step could leave the tree breaking one of the red-black rules, the one that says every path from the root down passes through the same number of black nodes. A specific sequence of placing, filling and cancelling orders could then trigger a rotation that detached live, unfilled orders from the root, and could even make orders that had already executed reachable again.
The check that decides whether an order is still live sits in OrderStatisticsTreeLib. From the pre-fix source in Secured Finance's GitHub repository, at commit 8cb2c2e6, which the postmortem pins as the code running before the fix:
exists() starts at an order's price node and walks up through its parents. If any parent no longer lists the node as its left or right child, or the walk runs out of parents before reaching the root, it returns false. isActiveOrderId() calls an order active only if the order is still in its node's list and that node passes exists(). In OrderBookLib, getLendOrderIds and getBorrowOrderIds sort a user's orders on exactly this answer, and every order that comes back inactive goes into the list that cleanUpOrders settles as filled, crediting the maker with the order's amount converted to future value at its own unit price.
That conversion is what turned a bookkeeping bug into a drain. A lending order's future value scales inversely with its unit price, so the postmortem notes that an order at 1% of face value gets credited with 100 times its principal, and an order at 3 out of 10,000 is credited with more than 3,000 times its principal. On the borrowing side, the same misreading could record debt and add borrowed principal to a deposit balance without any lender on the other side. Those inflated entitlements fed the collateral and withdrawal checks, and real tokens left the TokenVault. The postmortem says no tokens were minted and no price oracle was manipulated.
A tree that loses a node behaves like a coat check where a hook falls off the rack and the attendant, unable to find your coat, decides it must already have been handed back. The rule the system needed runs the other way, since an order that can't be found is no evidence of a fill.
Defimon's alert on September 7 described the bug as a price problem, reading the attack as self-trades that pushed getMarketUnitPrice(), the protocol's same-block market price, to par so a fake lending position counted as real collateral. The DeFiHackLabs proof of concept for the WBTC drain follows the same reading. The events of transaction 0xcd61...b1da on Blockscout fit the team's account instead. There's one par-priced trade in the log, 0.9 WBTC that the bot borrowed from a second address it funded in the same transaction, and the extra value shows up in OrdersCleaned events for orders that never matched.
The bot took a 0.91 WBTC flash loan from Balancer and deposited it into the TokenVault. It placed six tiny lending orders at unit prices of 3, 9,500, 1, 9,200, 9,300 and 2, the first of them for 0.00099998 WBTC, cancelled one, and sold 2 units into the book. It then placed two more lending orders at 9,400 and 9,100, sold 1 more unit and cancelled another order. OrdersCleaned then reported orders 3, 10 and 9 as filled for 100,000 units of principal and 333,326,669 units of future value, about 3.33 WBTC. The fills in the log only reached order 9, and order 3 was the one at unit price 3.
With that credit in place, the bot withdrew 0.909 WBTC, routed 0.9 WBTC through its second address into a par-priced loan to itself, and withdrew that 0.9 WBTC too. It then placed a cluster of borrow orders, including one for 0.89999 WBTC at 9,700, filled just 15 units against them and cancelled several. OrdersCleaned reported orders 17 and 14 as filled for 89,999,004 units, and the TokenVault paid out 0.89999104 WBTC. The bot repaid Balancer's 0.91 WBTC and swapped the remaining 0.89899003 WBTC for 28.85 WETH on Uniswap v3. None of the transaction's events is an oracle update.
Defimon's timeline starts at 21:26 UTC, when an exploit contract was deployed and its monitoring flagged it. The first drain landed at 22:11:23 in block 25914035, a USDC withdrawal of about $3,000 that matches the earliest outflow in Secured Finance's postmortem. At 22:42 the flagged wallet's own attempt reverted after running out of gas, and 48 seconds later c0ffeebabe.eth's generalized frontrunner, a bot known for white-hat front-runs, ran the WBTC drain. It forwarded about 28.8 ETH to the ultrasound.money builder and, per Defimon, kept about $29. Another USDC drain followed at 23:26.
Secured Finance didn't learn of the incident until 02:29 UTC on September 7, when a routine administrative job failed, about 28 hours after the first outflow. The postmortem notes that the third-party anomaly service it had used was wound down before the attack. The team paused its markets and TokenVault on all three chains between 07:55 and 08:07, posted its first notice at 08:12, and brought in Quantstamp to reproduce the attack on a mainnet fork, review the fix and trace funds. It later counted 14 withdrawals and one liquidation of a team member's account.
On September 10, c0ffeebabe.eth arranged through Ultra Sound to return 29.9823 ETH, and Ultra Sound later returned another 1.5724 ETH in builder fees, 31.5547 ETH in all, which the team valued at about $78,000. The tree fix went live on September 24. An October check found stale records of executed orders still reachable in eight order books and cleared 17 of them, and the protocol reopened on all three chains on October 16. Secured Finance says no user outside its team lost funds and that the Foundation bore the loss.
The order-book library went through Quantstamp audits in November 2023 and March 2024 and ran on public chains from December 2023 without incident. According to the postmortem, an internal security review found the rebalancing defect in late May 2026, and a fix was written and tested in June. The fix commit in the repository, f3b7a169, is dated June 17. The team didn't recognize that the defect could create fictitious claims that would drain the TokenVault, so deployment waited for an external audit, a decision the postmortem itself calls wrong. The defect lived in a data-structure library, while the damage happened in settlement and withdrawal logic in other contracts, the kind of seam where separately reviewed pieces meet.
Olympix reads a protocol the way an attacker does, as states, transitions and entry points, and it takes in the context around the code, such as how orders are meant to be settled, to infer the invariants the system relies on. In Secured Finance the critical state is the shape of each order-book tree and the list of each maker's open orders. The critical transitions are the bulk drops, cancels and inserts that reshape the tree, and the cleanup and withdrawal paths that turn the tree's answer into balances.
The invariant here is simple to say: an order that hasn't been filled is never settled as filled. The signal is code that decides a fill by absence, an order counted as executed because exists() can't reach its node, sitting downstream of hand-written tree surgery in dropLeft and dropRight that relinks parents and children directly. From that signal the engine narrows from every path through the order book, to the feasible sequences of place, fill and cancel calls, to the ones that end in cleanUpOrders crediting a maker for an order no taker ever matched.
Two methods attack a flagged path like that one. BugPoCer runs Counterexample-Guided Invariant Refutation, working outward from the system, inferring the settlement rule and executing call sequences until one breaks it. BugScout runs Bounded Adversarial Verification, working inward from the signal with known attack strategies. Both draw on symbolic execution to reason about which sequences are reachable. The output is a counterexample: a short run of order placements at chosen prices, a partial fill that triggers a bulk drop, a cancel that sets up the bad rotation and a cleanup that reports an untouched order as filled, along with the minimal tree shape that makes it happen and a generated Forge test that replays it. That's the September 5 transaction, produced in development instead of on mainnet.
The engine runs automatically in development, at every merge and before release, so a counterexample like this one would have surfaced at the merge that introduced the bulk-drop rebalancing, and again in June, when a fix sat ready while the question of what the bug could cost was still open. Audits cover the scope they're given in the time they have, and a standing check of the settlement invariant across the tree, the order book and the vault answers that question with a concrete trace.
Secured Finance's tree, its settlement code and its vault each did what their authors expected, and the money moved because one of them read a missing node as a finished trade. For anyone building order books or lazy settlement, every entitlement should trace back to a recorded fill, never to the absence of an open order. To see how Olympix checks that rule across your order book and hands back a replayable test wherever it breaks, book a technical walkthrough.
Protocol: Secured Finance (Secured.Fi) lending market
Chain: Ethereum, Arbitrum, Filecoin (the references below are from the Ethereum leg)
Date: September 5, 2026, first drain at 22:11:23 UTC, WBTC drain at 22:43:11 UTC
Loss: $180,856 across all three chains (Secured Finance postmortem); about $104K on Ethereum (Defimon); largest single drain 0.89899003 WBTC, about $72K
Vulnerable code: OrderStatisticsTreeLib bulk removal (dropLeft, dropRight) and the exists() / isActiveOrderId() check used to settle orders
TokenVault (drained): 0xB747...9393
LendingMarketController: 0x35e9...9Fe2
WBTC order book (LendingMarket): 0x2Dcb...6feB
First drain tx (block 25914035): 0x6fdb...2b9a
Reverted attacker attempt (block 25914188): 0x5d4e...2777
WBTC drain tx (block 25914192): 0xcd61...b1da
c0ffeebabe.eth: 0xc0ff...9671
c0ffeebabe.eth bot contract: 0xE08D...D015
Return of 29.9823 ETH (Sept 10): 0xc626...489d
Return of 1.5724 ETH builder fees: 0x17d0...67dc
Secured Finance: Postmortem, the September 2026 Lending Protocol Incident and Our Recovery
Secured Finance GitHub: OrderStatisticsTreeLib.sol at pre-fix commit 8cb2c2e6
Secured Finance GitHub: Fix commit f3b7a169, red-black tree balance handling in drop operations
Defimon: Secured.Fi alert on X