Flash Loan Attack Detection: Protecting DeFi Protocols

Flash loans are often framed as the vulnerability. They are not. They are a capital amplifier for vulnerabilities that already exist.

Beanstalk would have been vulnerable to governance takeover even without flash loans — it would just have required the attacker to own enough BEAN tokens. Flash loans removed that capital requirement. The attacker borrowed $1 billion in crypto assets, used the borrowed tokens for voting power, executed a malicious governance proposal that drained the treasury, and repaid the loan in the same transaction. The governance design was the problem. The flash loan was a free way to exploit it at scale.

This distinction matters for detection. The question is not “does this contract interact with a flash loan provider?” It is “does this contract have operations that become dangerous when combined with large temporary capital?”

What Flash Loans Actually Do

A flash loan is a loan with no collateral requirement, provided the loan is repaid within the same transaction. If repayment fails, the entire transaction reverts — including any state changes from the exploit. Aave, Balancer, and Uniswap V3 each provide flash loan facilities. Aave V3’s interface:

// Borrow $100M in one transaction
aavePool.flashLoan(
    address(this),   // receiver — must implement executeOperation()
    assets,          // tokens to borrow
    amounts,         // amounts to borrow
    modes,
    onBehalfOf,
    params,
    referralCode
);

The pool calls executeOperation() on the receiver contract with the borrowed funds. Whatever the attacker does in that callback runs with full access to the capital. At the end of the callback, the loan plus fee must be present in the pool address, or the transaction reverts.

The attacker assumes zero risk if the exploit fails. If the transaction reverts, no state changes persist, including the attack. The only cost is gas.

The Three Exploit Families

Oracle Manipulation

Flash loans most commonly combine with oracle vulnerabilities. A protocol reading instant AMM reserves for pricing is vulnerable to any attacker with enough capital to move those reserves. Before flash loans, “enough capital” was a meaningful barrier. Now it is not.

Cream Finance’s $130 million loss in October 2021 followed this pattern. The attacker borrowed $2 billion across multiple flash loan sources simultaneously, used the capital to manipulate the yUSD vault token price through large swaps, and borrowed against the inflated collateral value. The oracle read manipulable spot prices from AMM pools without time-weighting. Flash loans provided the capital to make that manipulation profitable at scale.

See the Oracle Manipulation post for the technical details of the oracle side. From a flash loan detection perspective, the relevant signal is: a flash loan callback that includes an oracle read and a subsequent financial operation based on that oracle price.

Governance Takeover

Beanstalk’s April 2022 loss of $182 million came from governance that used current token balances as voting power. Any governance design where voting power is proportional to a liquid, borrowable asset creates flash loan risk.

The safe alternative is snapshot-based voting: measure voting power at a specific past block rather than the current block. If your governance counts token.balanceOf(msg.sender) at the time of the vote, it is flash-loan vulnerable. If it counts token.getPriorVotes(msg.sender, block.number - 1), the attacker cannot borrow tokens in the same block they vote.

// Vulnerable: reads current balance
require(token.balanceOf(msg.sender) >= PROPOSAL_THRESHOLD);

// Safe: reads prior block snapshot
require(
    token.getPriorVotes(msg.sender, block.number - 1) >= PROPOSAL_THRESHOLD
);

The detector flags governance functions (proposals, votes, executions) that read current token balances rather than historical snapshots, especially when the contract also interacts with flash loan providers.

Reward and Share Inflation

A subtler attack class targets protocols that calculate rewards or share values using token.balanceOf(address(this)) — the protocol’s own token balance. An attacker can flash loan a large amount of the token, donate it to the protocol (inflating balanceOf), claim inflated rewards, withdraw the donated amount, and repay the loan.

Pancake Bunny’s $45 million loss in May 2021 followed this mechanism. The attacker flash loaned BNB, swapped into BUNNY to inflate the pool price, triggered a massive reward mint at the inflated price, sold the minted BUNNY, and repaid the loan.

The fix is to track deposits explicitly rather than using balanceOf for accounting:

// Vulnerable: uses live balance
uint256 reward = userShares[msg.sender] * token.balanceOf(address(this)) / totalShares;

// Safe: uses tracked internal accounting
uint256 reward = userShares[msg.sender] * trackedDeposits / totalShares;

How Detection Works

At the bytecode level, flash loan detection starts with selector identification. Each major flash loan provider exposes a specific interface:

  • Aave V3: flashLoan()0xab9c4b5d
  • Balancer: flashLoan()0x5c38eb3a
  • Uniswap V3: flash()0x490e6cbc
  • EIP-3156 standard: flashLoan()0x5cffe9de

A contract that calls any of these selectors is either a flash loan user or a flash loan provider. Contracts that implement the corresponding callback selectors (executeOperation, receiveFlashLoan, uniswapV3FlashCallback) are receiving flash loans.

After identifying flash loan involvement, the detector traces control flow through the callback to identify what operations execute with borrowed funds. The high-risk patterns:

Oracle reads without manipulation resistance: A getReserves() or latestRoundData() call inside a flash loan callback, where the result feeds into a financial calculation, is flagged as a potential oracle manipulation vector.

Governance operations using current balance: A balanceOf() call that feeds into a governance threshold check inside a flash loan callback indicates potential flash loan governance attack.

Share calculations from live balance: A balanceOf(address(this)) that feeds into reward or share calculations indicates donation attack risk.

Finding: Flash Loan with Oracle Manipulation Risk
Severity: CRITICAL
Confidence: 0.89

Flash loan provider: Aave V3 (selector 0xab9c4b5d)
Callback: executeOperation() at offset 0x3A2

Risk identified:
  Oracle read: getReserves() at offset 0x4B1 (Uniswap V2 spot price)
  Usage: collateral valuation in borrow() at offset 0x512
  Missing protection: no TWAP, no staleness check

Similar exploits:
  Cream Finance (Oct 2021) — $130M
  Venus Protocol (May 2022) — $11M

Recommendation: Replace spot price oracle with TWAP (minimum 30-minute window)
  and add Chainlink price feed as secondary source

The Bytecode Advantage

Flash loan detection on source code has a fundamental problem: flash loan callbacks frequently involve multiple contracts and interfaces. Static analysis on source code struggles to trace execution across interface boundaries without knowing the concrete implementations.

Bytecode analysis operates on the deployed contracts, tracking actual control flow through CALL and DELEGATECALL opcodes rather than interface declarations. The detector identifies flash loan selectors in the compiled output directly — regardless of what the original Solidity interfaces were named, regardless of compiler optimizations, regardless of whether source is available.

A contract can implement the Aave flash loan callback as executeOperation, wrap it, inline it via an optimizer, or rename it — the bytecode will still contain a function entry point that matches the executeOperation selector, because the pool contract calls it using that selector.

Common Mitigations

For oracle manipulation: Use TWAP oracles with windows appropriate to the asset’s liquidity depth. Validate Chainlink feeds with staleness checks. Use multiple oracle sources with deviation bounds.

For governance: Implement snapshot-based voting. Require voting power to be measured at a prior block. Consider time-locked governance with voting delays.

For reward/share calculations: Use internal accounting variables rather than live balanceOf calls. Never let an externally-donated balance affect reward calculations.

For general flash loan defense: If a protocol must prevent flash loans from interacting with a specific function, track entry using a storage slot: set a flag at function entry, check for that flag in sensitive operations, and detect re-entry across transactions using block number checks.

References

  1. Aave Flash Loan Documentation
  2. EIP-3156: Flash Loan Standard
  3. SWC-107: Reentrancy