October 20, 2026
|
Exploit Postmortem
Cover image for "FlashLoopAdapter's $305K Loss and How Olympix Would Have Prevented It"

FlashLoopAdapter's $305K Loss and How Olympix Would Have Prevented It

Date: October 1, 2026

Chain: Ethereum

Loss: 114.096 WETH, about $305K

Class: a caller check that asked the caller to vouch for itself, plus a raw call to a caller-chosen target

At 14:15 UTC on October 1, 2026, a fresh Ethereum wallet received 0.049875 ETH out of Railgun, a privacy protocol. Less than an hour later that wallet sent one transaction to a contract it had written, and when the transaction finished, two Safe multisigs had lost about 1,313 weETH of collateral and the wallet held 114.096 WETH, worth about $305,000.

The Safes had never signed anything for the attacker. They had enabled FlashLoopAdapter, a third-party module for running leveraged Aave v3 positions, and the adapter decided who was allowed to use it by asking the caller whether the adapter was enabled on the caller. A contract that answered yes to that question could make the adapter call into any Safe that had really enabled it.

A Shortcut for Leveraged Loops

A leveraged loop on Aave v3 means depositing collateral, borrowing against it, swapping the borrowed asset into more collateral and repeating, then unwinding the same steps in reverse. Doing that from a Safe multisig normally takes a signed owner transaction for every step. FlashLoopAdapter, deployed in July 2026, was a Safe module built to open and close those positions in one call, using flash loans to do the borrowing and repaying inside a single transaction.

Safe modules are how that becomes possible. When a Safe's owners enable a module, the module can call execTransactionFromModule on the Safe and have the Safe execute a transaction with no further signatures. That's a lot of trust, so a module has to be careful about who it acts for. FlashLoopAdapter's open() and close() functions were meant to act only for a Safe that had enabled the adapter, on that Safe's own position.

A Check the Caller Answered for Itself

According to SlowMist, open() and close() authenticated the caller with one test, whether ISafe(msg.sender).isModuleEnabled(address(this)) returned true. msg.sender is whoever is calling. A real Safe answers that question honestly, but any contract can implement a function called isModuleEnabled that returns true, and the adapter had no other way of knowing whether the caller was a Safe at all.

That alone let a stranger reach the adapter's logic. The second flaw decided what that stranger could do with it. The adapter's internal _swap() made a raw low-level call to a swapRouter address with swapCalldata, and both came from the caller, with no allowlist on the target and no limit on the data. The swap step was supposed to hit a DEX router, but nothing stopped the caller from pointing it at a victim Safe and filling the calldata with execTransactionFromModule. The victim Safe would see a call from a module it had enabled and do what it was told.

Picture a bank's phone line that confirms a caller's identity by asking whether they're an authorized signer on the account, and accepts a yes. The same line then lets that caller dictate a payment order on the bank's letterhead to any business that banks there, and those businesses pay because the letterhead is real. The idea to take from this case is that a contract has to establish who its caller is from state it controls, and anything it forwards on a caller's behalf runs with the contract's own authority, so the target and the data both need limits.

One Fake Safe, Two Real Ones

The attacker's wallet, 0x42c2...9353, deployed a helper contract at 14:44 UTC and sent the exploit transaction at 15:08:47 UTC in block 26,098,264, to an attack contract at 0xF091...67ff. The DeFiHackLabs proof of concept describes that contract as playing several parts at once, a fake Safe that answers yes to the module check, a fake flash-loan provider and a fake pool, with no-op behavior everywhere else.

The transaction's token transfers show the rest. The attack contract borrowed 11,537.24 WETH from Morpho Blue in a flash loan and called the adapter as the fake Safe, setting the swap router to a real victim Safe and the calldata to execTransactionFromModule. Through that path it repaid 1,335.26 WETH of Aave debt owed by the first victim Safe, 0xCFeD...169f, which freed its collateral, and moved the Safe's 1,306.48 weETH of Aave collateral to the attack contract. It then pulled 6.43 weETH that the second Safe, 0xE3b2...8520, held directly. The weETH was swapped back to WETH through a Curve weETH/WETH pool and ParaSwap's Augustus router, the flash loan was repaid, and 114.096 WETH went to the attacker's wallet.

Defimon's alert put the loss at about $305,000. The PoC reproduces the 114.096 WETH net profit exactly, and SlowMist's figure of about 114.09 ETH is the same profit net of gas.

Into Railgun, Then a Message on Chain

At 16:45:59 UTC, the attacker sent 114.13 ETH to a second wallet, 0x951A...2f1a, which forwarded 114.03 ETH into Railgun a minute later. That's the same privacy protocol that funded the attack.

Aave's founder Stani Kulechov described the contract as a "third party external adapter built on top of Aave" with "zero effect on Aave v3," as Cointelegraph reported, and SlowMist confirmed Aave v3's own contracts weren't affected. At 21:03 UTC, an address registered as aavechan.eth sent the attacker an on-chain message saying its owner held the two drained Safes, offering a 10% whitehat bounty of 11.41 ETH if the remaining 102.69 ETH came back by October 3 at 18:00 UTC. As of October 19, 2026, that address, 0x329c...3eD4, hadn't received any ETH back on chain since the message went out.

How Olympix Spots Authentication That Comes From the Caller

Olympix reads a contract the way an attacker does, as states, transitions and entry points, and takes in the context around it, here a Safe module whose whole job is to act with the authority Safes delegate to it. From that it infers the invariants the system depends on. For FlashLoopAdapter the invariant is one sentence long: the adapter only moves a Safe's assets when that Safe itself initiated the call.

Olympix's static analysis surfaces two signals in this contract. The first is an authorization check whose only input is a value returned by msg.sender, so the caller is grading its own test. The second is a low-level call whose target and calldata both arrive from the caller with no allowlist, sitting in a contract that other contracts trust as a module. Either signal narrows the search from every path through open() and close(), to the feasible ones, to the paths where the adapter ends up calling a Safe that never called it.

BugPoCer starts from the invariant and works outward through the adapter's entry points, using Counterexample-Guided Invariant Refutation to look for any sequence where a Safe's assets move without that Safe calling in. The counterexample here needs nothing exotic: a contract whose isModuleEnabled() returns true calls open(), names a victim Safe as the swap router and passes execTransactionFromModule as the calldata, and the victim's collateral leaves. BugPoCer returns that as a concrete sequence of state changes with the minimal conditions that trigger it and a generated Forge test that replays it, which is the October 1 drain produced in development.

Olympix has flagged both classes before in other codebases, missing access control and arbitrary external calls to caller-supplied targets. Integration contracts like this one sit between protocols that each work correctly on their own, and the gap only shows up in what the integration accepts as proof of identity. The engine runs in development, at every merge and before release, so that question gets asked of every entry point each time the code changes.

A Module Holds Other People's Keys

FlashLoopAdapter was trusted by real Safes to act on their behalf, and it extended that trust to any contract willing to claim it. Teams building modules, adapters and routers on top of protocols like Aave and Safe should treat every value the caller supplies, including its answers to questions about itself, as attacker-controlled, and constrain every call made in their own name. To watch Olympix test the authorization paths in your own modules and return a runnable counterexample for any that let the wrong caller through, book a technical walkthrough.

On-Chain References

Protocol: FlashLoopAdapter, a third-party Safe module for Aave v3 leveraged loops

Chain: Ethereum

Date: October 1, 2026, 15:08:47 UTC

Loss: 114.096 WETH (about $305K)

Attacker EOA: 0x42c2...9353

Attack contract (fake Safe): 0xF091...67ff

FlashLoopAdapter (victim module): 0x16bb...83d8

Victim Safe 1 (Aave position): 0xCFeD...169f

Victim Safe 2 (weETH holdings): 0xE3b2...8520

Exploit tx: 0x7532...6fc4

Block: 26,098,264

Funding tx (from Railgun, 14:15:23 UTC): 0xc5dc...852b

Transfer to second wallet: 0x3a56...53f9

Second wallet: 0x951A...2f1a

Deposit into Railgun: 0xa206...6dc9

On-chain bounty message from aavechan.eth: 0x94b6...89d0

Flash loan source (Morpho Blue): 0xBBBB...FFCb

Sources

SlowMist on X: FlashLoopAdapter Exploit Analysis

SlowMist on X: Aave V3 Contracts Not Affected

Defimon Alerts on X: FlashLoopAdapter Exploit Alert

DeFiHackLabs PR #1287: FlashLoopAdapter Exploit PoC

Cointelegraph: Aave V3 Third-Party Adapter Exploit Drains $305K

More from Olympix:

No items found.

Ready to Shift Security Assurance In-House? Talk to Our Security Experts Today.