TL;DR
On October 17, 2026, at 05:43 UTC, BiFi's Ethereum USDT market lost 165,575 USDT in a single transaction. The market had been idle for about 264,000 blocks, roughly 36 days. Defimon's analysis, which BiFi's verified source code supports, is that the accrual code stores that gap once per block and then re-charges all of it on every later action in the same block, at an interest rate read from live deposit and borrow totals. A caller who controls the totals for one block controls what 36 days of interest is worth. An address deployed an exploit contract, and 12 seconds later, in the next block, the MEV operator c0ffeebabe.eth executed the drain from its own contract, which Defimon describes as a front-run of that deployer. It used a 40.5M USDT flash loan from Morpho and took the profit as 68.06 WETH, of which 0.99 ETH went to the block builder. About 36 hours later c0ffeebabe sold the 67.07 ETH it kept and sent all 159,103.65 USDC of proceeds back to BiFi's USDT handler. Within 12 minutes of that return, address poisoners were spraying lookalikes of BiFi's address into c0ffeebabe's history. BiFi has not published a postmortem.
What Happened to BiFi's USDT Market?
The monitoring service Defimon flagged it first. Its alert reported that BiFi's USDT market "had been idle for about 264K blocks (~36 days)", that TokenHandler._checkFirstAction stores that gap as inactiveActionDelta and freezes actionEXR once per block, and that every later action in the block re-runs the interest calculation against it.
The chain agrees with the idle period. The last transaction sent directly to BiFi's UsdtHandlerProxy before the attack was a repay on September 10, in block 25,946,196. The attack landed in block 26,210,256. That is 264,060 blocks.
Two contracts were involved, 12 seconds apart. At 05:42:59 UTC the attacker's address 0x7c78a049...6ec4c6 deployed an exploit contract in block 26,210,255. At 05:43:11, in the very next block, c0ffeebabe.eth executed the drain from its own contract. The attacker's contract has exactly one transaction on record, its own creation. It never got to run.
How Does BiFi Accrue Interest?
BiFi tracks interest with exchange rates. A depositor's balance is stored next to the market's exchange rate (EXR) at the moment of their last action. Later, the market's current EXR is compared with the stored one, and the difference times the balance is the interest owed or earned. That is EXR accrual, and it is the same basic idea Compound made standard, though BiFi's code is its own.
The update rule lives in BiFi's interestModel contract. _getNewGlobalEXR returns interestRate.mul(delta).add(unifiedPoint).unifiedMul(actionEXR). The new rate is the frozen starting rate times one plus the per-block interest rate times the number of blocks elapsed. Defimon's alert writes the same thing as globalEXR = actionEXR × (1 + rate × 264K).
In that formula delta is time and interestRate is price, and a correct accrual uses the price that actually prevailed during the elapsed time.
Why Did a 36-Day Gap Become an Exploit?
In the TokenHandler contract, _checkFirstAction computes blockDelta as the current block minus the last updated block. If it is non-zero, it calls setBlocks(currentBlockNumber, blockDelta) and syncActionEXR(), then returns. On the first action of a block, that writes the gap into storage as inactiveActionDelta and snapshots the starting EXR. Every later action in the same block finds blockDelta at zero and skips the update, so the stored gap and the frozen snapshot stay in place.
Then _getInterestAmount in the interest model reads that stored delta, all 264,060 blocks of it, and passes it to _calcInterestAmount. That function calls _getSIRandBIRonBlock with the market's deposit and borrow totals as they stand right now, mid-transaction. The supply rate scales with utilization, borrows divided by deposits.
So within one block, time is fixed at 36 days and price is whatever the current caller has made it. Each action then re-marks the caller's deposit and debt to that price. _updateInterestAmount stores the freshly computed EXR as the user's own, and _getDeltaEXR returns a signed difference, so a balance can be marked up or down. Every re-mark is a rate change multiplied by 264,060 blocks, which turns a small move in utilization into a large move in a balance. On our reading of the code, a caller can deposit at low utilization, borrow heavily to push utilization up, and have the deposit credited a month of interest at a rate the caller created a few calls earlier.
Compound avoids this. Its accrueInterest returns immediately when accrualBlockNumberPrior == currentBlockNumber, and it computes the rate from cashPrior, borrowsPrior, reservesPrior, the state before the action. The elapsed time is charged once per block, at the price that existed before anyone touched it. BiFi charged it on every call, at a price the caller was setting.
The longer a market sits idle, the bigger delta gets. A busy market resets the gap every few blocks and the bug is worth almost nothing, while an idle one lets the gap grow.
The Attack, Step by Step
Our reconstruction from the replay transaction's token transfers, all inside one transaction:
| Step | Action | USDT amounts |
|---|---|---|
| 1 | Flash-loan USDT from Morpho | 40,500,000 in |
| 2 | Deposit into the BiFi handler in five growing tranches, roughly quadrupling each time | 121,257 / 484,545 / 1,795,239 / 7,238,802 / 30,860,157 |
| 3 | Borrow back out of the same market in 20 equal draws | 20 x 1,498,500 (29.97M) |
| 4 | Repay and withdraw in a loop, each pass paying 1,498,500 in and taking 2,025,000 out | 20 passes |
| 5 | Close the cycle with a final repay and withdraw | 165,164 in / 247,951 out |
| 6 | Run steps 2 to 5 a second time with identical amounts | Second cycle |
| 7 | Swap the surplus on a Uniswap V3 pool | 165,574.51 USDT for 68.06 WETH |
| 8 | Repay Morpho and unwrap the WETH to ETH | 40,500,000 out |
Each cycle pushed about 70.6M USDT into the handler and pulled about 70.7M out. The difference was 82,787.58 USDT per cycle. Twice is 165,575.15 USDT, which is exactly the handler's net outflow in the transaction.
The debt's interest should offset part of the deposit's credit. Our inference from the code, not from a replay, is that ordering beats it. The full 40.5M deposit is in place before utilization starts to climb, and the debt arrives in twenty slices of 1,498,500. Each slice raises utilization, and the rate jump that follows is credited on the whole 40.5M deposit but charged only on the debt drawn so far. The repay and withdraw loop then swings utilization back and forth, re-marking both balances on every pass. We did not replay the transaction on a fork, so we cannot split the 82,787.58 USDT per cycle between the borrow phase and the loop. The transfers show the result, not the internal ledger.
The transaction's Etherscan record shows 15,617,818 gas used, a transaction fee of 0.000845755321487776 ETH, and position 1 in its block. Inside the same transaction, c0ffeebabe's contract paid 0.98682944511992231 ETH to Titan Builder and forwarded 67.070373666598867051 ETH to c0ffeebabe.eth. The real price of the drain was that builder payment, about 1.45% of the take, to sit first in the block. By our arithmetic, roughly 141M USDT of turnover produced a 0.12% edge, and the edge was nearly everything the market held.
Who Actually Ran the Drain?
Defimon reads the block order as a front-run: the deployer at 0x7c78a049...6ec4c6 found the bug, and c0ffeebabe.eth spent it. The chain fits that reading and shows nothing that contradicts it.
Defimon's account is that "one block later, c0ffeebabe.eth front-ran it, replaying the attack from its own contract." The on-chain order fits: deployment in block 26,210,255, replay in 26,210,256, attacker's contract never called. How c0ffeebabe's system turned a freshly deployed contract into a working replay within 12 seconds is not public.
This operator has done it before. In July 2023, during the Curve pools exploit, c0ffeebabe.eth front-ran a malicious transaction and returned 2,879 ETH, about $5.4 million to the Curve deployer.
At 09:27 UTC the same day, a second address ran a transaction through the handler that netted 1.52 USDT.
What Came Back, and What Did Not?
On October 18, c0ffeebabe sold 67.07 WETH through CoW Protocol's settlement contract for 159,103.65 USDC. Minutes later, at 17:21:59 UTC, it sent all 159,103.65 USDC to BiFi's UsdtHandlerProxy.
Defimon's alert headline says $165.4K, and its body says 165.6K USDT. The handler's net outflow is 165,575.15 USDT on-chain, and that is the number we use. Against it, 159,103.65 USDC came back, a gap of 6,471.50 dollars of stablecoin.
Our arithmetic on the two swaps accounts for all of it. About 0.99 ETH, worth roughly 2,400 at the drain's swap rate, went to Titan Builder inside the replay. The rest is ETH's price move: the drain sold USDT at about 2,433 per WETH, and the return bought USDC at about 2,372 per WETH, which on 67.07 ETH costs roughly 4,100. c0ffeebabe received 67.07 ETH and sold 67.07 WETH for the refund. On the visible record it returned everything it kept. BiFi has said nothing publicly.
There is a smaller oddity. The money went back as USDC to a contract built to hold USDT. The handler's current balances show 159,103.65 USDC sitting next to 2,986.41 USDT. BiFi has not said how it will move a token its USDT market does not account for.
Why Did Address Poisoners Show Up Within Minutes?
A large, public refund is a target. At 17:30:11 UTC, eight minutes after the 17:21:59 return, the lookalike address 0x808CA0d0...0BA9E3 sent c0ffeebabe 0.000159 USDC. BiFi's real handler is 0x808C3bA9...67A9E3. Same first four hex characters, same last four, and the dust amount mirrors the 159,103 figure.
At 17:33:59, twelve minutes after the return, a single transaction emitted dozens of fake token transfers. One of them shows "159,103.654838" of a token whose symbol is USDC written with lookalike Unicode characters and zero-width spaces, apparently moving from c0ffeebabe to 0x808c4c3D...D7A9e3. That is another lookalike of BiFi's handler. c0ffeebabe never signed it. A token contract can emit a Transfer event naming any sender it likes.
If c0ffeebabe, or BiFi, or anyone tidying up the recovery copies "the address that received 159,103 USDC" from a transaction history, a lookalike is now sitting right there with the same number beside it.
What Does This Mean for Lending Protocols That Accrue by Exchange Rate?
Accrual must be idempotent within a block, and the rate it charges for elapsed time must come from state the caller cannot change in the same transaction.
As an invariant test: take a market, roll the chain forward N blocks with no activity, then in one block perform any sequence of deposits, borrows, repays and withdrawals and close every position. The account's net take must not exceed the interest a passive depositor would have earned at the pre-action rate. Once that property exists, a fuzzer has a direct path to BiFi's bug.
This Ethereum handler had seen no direct transaction in five weeks, yet it still held real USDT and still accepted any caller. Old state paid out twice more on this blog in October, in different forms: a MakerDAO keeper's Black Thursday auction wins sat unclaimed for six years, and the Cetus attacker's stale Sui Bridge withdrawals sat for 17 months. Neither involved an idle market, but each was value parked in code nobody was watching until someone else noticed it first.
Rescue by front-run worked here because c0ffeebabe's bot reached the bug first and its operator chose to send the money back, and BiFi had no say in either step.
Monitoring would not have saved this market. The drain was one atomic transaction in position 1, so every signal inside it, the 40.5M USDT flash loan into a market that held roughly 168,600 USDT (the 165,575 drained, the 1.52 taken later and the 2,986 still there), the twenty borrows of 1,498,500, the 264,060-block gap ending in a burst of activity, becomes visible only after the money is gone. The one precursor was the deployer's contract, created one block, 12 seconds, before the drain. To stop the drain, an owner pause would have had to land in block 26,210,256 ahead of a transaction that had already paid the builder for position 1. That window did not exist, so for this market nothing could act before the money left. What would have prevented it is a review that asks how accrual behaves after a long idle period inside a single block, and that review only catches it if someone writes the property down. Our Solidity audit work puts lending accrual under invariant fuzzing with exactly that property: advance the chain through a long idle gap, run arbitrary same-block sequences of deposits, borrows, repays and withdrawals, and assert that no account walks away with more than passive interest. The replay took 82,787.58 USDT per cycle out of the market inside one block, which is exactly what that property forbids.
What Should Lending Operators Do Now?
- Make accrual run once per block, at the pre-action rate. Return early when the last accrual block equals the current block, and compute the rate from stored totals before the caller's action applies, as Compound's
accrueInterestdoes. - Write the idle-gap invariant as a fuzz test. Advance N blocks, run arbitrary same-block sequences, close out, and assert no net gain above passive accrual. Run it for N in the tens of thousands of blocks, not just N = 1.
- Inventory every market you no longer promote. If a deployment is not maintained, pause it, cap it, or drain it to users. A market with no traffic accumulates exactly the input this bug needed.
- Bound utilization swings inside the contract. A check that reverts when one transaction moves utilization past a set limit, or deposits hundreds of times the market's size and borrows most of it back, works inside the block, where an alert cannot.
- Plan the refund path before you need it. Publish one return address through a signed channel, and treat any address copied from transaction history as suspect. Poisoners showed up about eight minutes after the refund here.
Frequently Asked Questions
What happened to BiFi lending?
BiFi's Ethereum USDT market lost 165,575.15 USDT on October 17, 2026. One flash-loaned transaction re-priced 36 days of accumulated interest at a utilization rate the caller had inflated. c0ffeebabe.eth executed it and returned 159,103.65 USDC on October 18.
Was the BiFi exploit a flash loan attack?
Yes. The 40.5M USDT came from a Morpho flash loan and was repaid in the same transaction. The loan provided the size, and BiFi's accrual turned it into profit by charging a stored idle gap on every same-block action at a rate read from live totals.
Did BiFi get its money back?
No, not all of it. 159,103.65 USDC came back against 165,575.15 USDT taken, a shortfall of about 6,471. About 0.99 ETH of it was paid to the block builder inside the replay, and the rest is ETH's price move between the drain and the return swap.
What kind of audit would have caught the BiFi interest accrual bug?
One that writes the property down. Accrual after a long idle gap, exercised by same-block sequences, is a property test, and a fuzzer running it is pointed at this path. A review that only checks per-call arithmetic could miss it. Runtime monitoring could not have stopped it, because the drain was a single atomic transaction.
Who ran the BiFi drain?
c0ffeebabe.eth executed the drain transaction and later returned 159,103.65 USDC. Defimon reads it as a front-run of a different address, which had deployed an exploit contract one block earlier and never called it. The block order fits that reading, and c0ffeebabe returned funds the same way after the Curve exploit in 2023.
Sources / References
- Defimon Alerts: Defimon detected an exploit on BiFi.finance which lost $165.4K on Ethereum
- Etherscan: exploit contract deployment, block 26,210,255
- Etherscan: c0ffeebabe.eth replay transaction, fee, position and builder payment
- Etherscan: 159,103.65 USDC returned to BiFi UsdtHandlerProxy
- Blockscout API: replay transaction token transfers
- Blockscout API: replay transaction details
- Blockscout API: UsdtHandlerProxy transaction history
- Blockscout API: UsdtHandlerProxy token balances
- Blockscout API: attacker exploit contract transactions
- Blockscout API: second-address transaction netting 1.52 USDT
- Blockscout API: CoW settlement selling 67.07 WETH for USDC
- Blockscout API: lookalike-address dust transfer
- Blockscout API: fake USDC transfer spray
- Sourcify: BiFi TokenHandler verified source
- Sourcify: BiFi interestModel verified source
- compound-protocol: CToken.sol
- The Block: MEV bot runner 'c0ffeebabe.eth' returns $5.4 million amid Curve exploit



