One second of block time: replaying the Fence Finance TranchePool drain
On 27-28 October 2026, a TranchePool on Avalanche associated with Fence Finance, Valinor Digital and Oatfi lost 1,013,353 USDC, its entire balance.
Within hours two readings circulated. Blockaid described flash-loan minting of inflated tranche shares. SlowMist described a settlement bug at the UTC day rollover. We wanted to know which one the chain agrees with, so we replayed the attack on a local fork.
The two transactions
At 23:56:41 UTC the attacker's contract took a flash loan of 1,013,353.61 USDC from Aave v3. That is exactly the pool's balance.
Inside the callback it made five calls to the pool. One of them carries the same amount and a timestamp, 0x6ae13b00, which is 00:00:00 UTC on 28 October. The last call draws the full amount back out. Aave is repaid with a 506 USDC premium.
After this transaction the pool still holds its 1,013,353 USDC. Nothing has been stolen yet.
At 00:00:03 UTC, three seconds after midnight, the attacker called the same draw again. This time the pool paid out its real USDC.
The fork replay
We forked Avalanche C-Chain at the block just before midnight and impersonated the attacker. Then we sent the second transaction's exact calldata twice, changing only the block time:
- at 23:59:59 the draw reverts;
- at 00:00:00 it succeeds and the pool goes from 1,013,353.61 USDC to zero.
A fork at the real block, 00:00:03, reproduces the on-chain result to the unit. The same state and the same calldata were used in every run. The only variable is the clock, so the draw check depends on time.
That fits SlowMist's description: at the rollover, the pool's settlement logic treated the scheduled principal payment as received and subtracted it from outstanding principal, although no USDC had arrived. The draw check, funded principal minus outstanding principal, then reported a full line of capacity again.
Who was right
Both readings were partly right. A flash loan was used, but only to set the position up without putting capital at risk. The theft itself is the rollover: a due date was booked as a payment.
The lesson for anyone building credit or vault logic
Real-world credit brings real-world conventions on-chain: due dates, day counts, waterfalls. Each of them is a place where state can move without money moving.
The invariant that holds the line is short: Draw capacity can never exceed the assets the contract has actually received. Write it down, then fuzz it across the boundaries where time changes the answer:
- the day rollover;
- a due date equal to the current block timestamp;
- a due date chosen by the caller.
Status
Fence has sent the attacker an on-chain message offering a 10% recovery bounty for the return of 90% of the funds.
Addresses and transactions:
- pool
0xe25cb545bdd47a8ec2d08001cb5661b00d47621a - attacker
0x6ff1c3ffa622e1062168f3497a275be9cc2b1246 - setup tx
0x19ab145e7f771549ca2f1da4b1768d7f728834015b2480ddb5be19c9769899fd - drain tx
0x3f6b23292a2e9c2956c7ac56d0b1f79b0271b49edec2156d2675ddf1de058c77
The pool's source is not verified on the explorers, so this analysis rests on calldata, events, balances and the fork replay.
Sentinelleai reads smart contracts before launch, fuzzes declared invariants, and watches deployed contracts after. See how it works for Web3 teams.
Comments
No comments yet. Start the discussion.