# Flash Loan Attacks Explained — How Millions Get Stolen
Flash loans are one of DeFi's most innovative features — and one of its most exploited attack vectors. In 2021 alone, over $300 million was stolen through flash loan attacks.
Despite being a legitimate DeFi primitive, flash loans amplify existing vulnerabilities, allowing attackers to manipulate markets, oracles, and governance with zero capital upfront.
In this article, we'll dissect how flash loan attacks work, examine real-world exploits, and learn proven defense strategies.
What Are Flash Loans?
A flash loan is an uncollateralized loan that must be borrowed and repaid within a single transaction.
Key Characteristics
- No collateral required — anyone can borrow millions instantly
- Atomic transaction — loan + operations + repayment happen in one block
- Instant liquidity — access to massive capital without owning it
- Zero risk for lender — if not repaid, entire transaction reverts
Example (Aave Flash Loan)
interface IFlashLoanReceiver {
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool);
}
contract FlashLoanUser is IFlashLoanReceiver {
ILendingPool public lendingPool;
function executeFlashLoan(address asset, uint256 amount) external {
address[] memory assets = new address[](1);
assets[0] = asset;
uint256[] memory amounts = new uint256[](1);
amounts[0] = amount;
uint256[] memory modes = new uint256[](1);
modes[0] = 0; // No debt
lendingPool.flashLoan(
address(this),
assets,
amounts,
modes,
address(this),
"",
0
);
}
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external override returns (bool) {
// You now have amounts[0] of assets[0]
// Do something with the flash loaned funds
// ... your logic here ...
// Repay loan + fee
uint256 amountOwed = amounts[0] + premiums[0];
IERC20(assets[0]).approve(address(lendingPool), amountOwed);
return true;
}
}
Why Flash Loans Are Dangerous
Flash loans themselves aren't malicious, but they amplify existing vulnerabilities by providing:
Flash Loan Attack Vectors
1. Oracle Manipulation
Most common attack type. Manipulate price oracles to exploit protocols that rely on them.
Vulnerable Oracle Pattern
contract VulnerableLending {
IERC20 public tokenA;
IUniswapV2Pair public pair; // TokenA/WETH
// VULNERABLE: Using spot price from DEX
function getPrice() public view returns (uint256) {
(uint112 reserve0, uint112 reserve1,) = pair.getReserves();
return (reserve1 * 1e18) / reserve0; // Instant price
}
function borrow(uint256 amount) external {
uint256 collateralRequired = (amount * getPrice()) / 1e18;
require(tokenA.balanceOf(msg.sender) >= collateralRequired);
// User can borrow based on manipulated price
// ...
}
}
Attack Scenario
contract OracleAttack {
function attack() external {
// 1. Flash loan large amount of TokenA
uint256 loanAmount = 1000000 * 1e18;
flashLoan(address(tokenA), loanAmount);
}
function executeOperation(/*...*/) external {
// 2. Dump TokenA on DEX, crashing price
tokenA.approve(address(router), loanAmount);
router.swapExactTokensForTokens(
loanAmount,
0,
path,
address(this),
block.timestamp
);
// Price is now artificially low
// 3. Borrow massive amount from lending protocol
// (which thinks collateral is worth more due to low TokenA price)
lendingProtocol.borrow(exploitAmount);
// 4. Swap back to restore price
router.swapExactTokensForTokens(/*...*/);
// 5. Repay flash loan
tokenA.transfer(lender, loanAmount + fee);
// Attacker keeps the over-borrowed funds
}
}
Real example: Harvest Finance (2020) — $24M stolen via price manipulation on curve pools.
2. Governance Attacks
Flash loan massive governance tokens to pass malicious proposals.
Vulnerable Governance
contract VulnerableDAO {
IERC20 public govToken;
struct Proposal {
address target;
bytes data;
uint256 forVotes;
uint256 againstVotes;
bool executed;
}
mapping(uint256 => Proposal) public proposals;
// VULNERABLE: Snapshot at vote time, not proposal creation
function vote(uint256 proposalId, bool support) external {
uint256 votes = govToken.balanceOf(msg.sender);
if (support) {
proposals[proposalId].forVotes += votes;
} else {
proposals[proposalId].againstVotes += votes;
}
}
function execute(uint256 proposalId) external {
Proposal storage prop = proposals[proposalId];
require(prop.forVotes > prop.againstVotes);
require(!prop.executed);
prop.executed = true;
(bool success,) = prop.target.call(prop.data);
require(success);
}
}
Attack
function governanceAttack() external {
// 1. Flash loan governance tokens
flashLoan(address(govToken), 51% of supply);
// 2. Create malicious proposal
dao.propose(address(treasury), abi.encodeWithSignature(
"transfer(address,uint256)",
attacker,
treasury.balance
));
// 3. Vote with flash loaned tokens
dao.vote(proposalId, true);
// 4. Execute immediately (if no timelock)
dao.execute(proposalId);
// 5. Repay flash loan
govToken.transfer(lender, amount + fee);
}
Real example: Beanstalk (2022) — $182M stolen via governance takeover with flash loaned tokens.
3. Reentrancy Amplification
Flash loans provide capital for reentrancy attacks on poorly protected protocols.
function amplifiedReentrancy() external {
// 1. Flash loan large amount
flashLoan(10000 ether);
// 2. Deposit to vulnerable protocol
vulnerable.deposit{value: 10000 ether}();
// 3. Exploit reentrancy to withdraw repeatedly
vulnerable.withdraw(10000 ether);
// Reentrancy drains more than deposited
// 4. Repay flash loan
lender.transfer(10000 ether + fee);
// Keep the excess
}
4. Liquidation Manipulation
Manipulate collateral prices to trigger mass liquidations, then buy the collateral cheap.
Real-World Flash Loan Attacks
Harvest Finance (October 2020)
- Stolen: $24M
- Method: Curve pool price manipulation
- Flash loan from: Uniswap (no fee at the time)
- Result: Protocol survived but users lost funds
PancakeBunny (May 2021)
- Stolen: $45M
- Method: Price oracle manipulation on PancakeSwap
- Impact: BUNNY token crashed 96%
Cream Finance (August 2021)
- Stolen: $18.8M
- Method: Flash loan + price oracle manipulation on AMP token
- Repeat: Cream was hacked 3 times in 2021
Beanstalk (April 2022)
- Stolen: $182M
- Method: Flash loan governance attack
- Loans from: Aave ($1B borrowed)
- Execution: Passed malicious proposal, drained treasury
Euler Finance (March 2023)
- Stolen: $197M (later returned)
- Method: Flash loan + donation attack to manipulate debt tracking
- Largest DeFi hack of 2023
Prevention Strategies
1. TWAP Oracles (Time-Weighted Average Price)
Don't use spot prices. Use time-weighted averages that can't be manipulated in a single transaction.
contract TWAPOracle {
IUniswapV2Pair public pair;
uint256 public priceAverage;
uint256 public lastUpdate;
uint32 public constant PERIOD = 10 minutes;
function update() external {
uint32 timeElapsed = uint32(block.timestamp - lastUpdate);
require(timeElapsed >= PERIOD, "Too soon");
(uint256 price0Cumulative, uint256 price1Cumulative,) =
UniswapV2OracleLibrary.currentCumulativePrices(address(pair));
priceAverage = (price0Cumulative - price0CumulativeOld) / timeElapsed;
lastUpdate = block.timestamp;
price0CumulativeOld = price0Cumulative;
}
function getPrice() external view returns (uint256) {
return priceAverage; // Manipulation-resistant
}
}
Why it works: Flash loan attacks happen in one transaction. TWAP requires price changes over many blocks.
2. Chainlink Price Feeds
Use decentralized, external price oracles that can't be manipulated via on-chain trades.
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract SecureLending {
AggregatorV3Interface internal priceFeed;
constructor(address _priceFeed) {
priceFeed = AggregatorV3Interface(_priceFeed);
}
function getLatestPrice() public view returns (int) {
(
uint80 roundID,
int price,
uint startedAt,
uint timeStamp,
uint80 answeredInRound
) = priceFeed.latestRoundData();
require(timeStamp > 0, "Round not complete");
return price;
}
}
Benefits:
- Data aggregated from multiple sources
- Updated off-chain (not manipulable via flash loans)
- Battle-tested across billions in TVL
3. Snapshot Governance
For DAOs, use block-based snapshots taken before voting starts.
contract SecureGovernance {
mapping(uint256 => uint256) public proposalSnapshots;
function propose(/*...*/) external returns (uint256) {
uint256 proposalId = proposalCount++;
// Snapshot BEFORE voting starts
proposalSnapshots[proposalId] = block.number;
return proposalId;
}
function vote(uint256 proposalId, bool support) external {
// Use balance at snapshot block
uint256 votes = govToken.balanceOfAt(
msg.sender,
proposalSnapshots[proposalId]
);
// Flash loans after proposal creation don't count
}
}
4. Execution Delays
Add timelocks to prevent instant execution.
contract TimelockDAO {
uint256 public constant DELAY = 2 days;
struct Proposal {
uint256 eta; // Earliest execution time
// ...
}
function queue(uint256 proposalId) external {
require(proposalPassed(proposalId));
proposals[proposalId].eta = block.timestamp + DELAY;
}
function execute(uint256 proposalId) external {
require(block.timestamp >= proposals[proposalId].eta);
// Execute
}
}
Flash loans must be repaid in one transaction, so they can't wait 2 days.
5. Flash Loan Detection
Block flash loan usage for sensitive functions.
contract FlashLoanProtected {
mapping(address => uint256) private balanceSnapshot;
modifier noFlashLoan() {
uint256 balanceBefore = balanceSnapshot[msg.sender];
// First interaction in this transaction
if (balanceBefore == 0) {
balanceSnapshot[msg.sender] = token.balanceOf(msg.sender);
} else {
// Balance shouldn't spike in same transaction
require(
token.balanceOf(msg.sender) <= balanceBefore * 2,
"Flash loan detected"
);
}
_;
}
function sensitiveAction() external noFlashLoan {
// Protected
}
}
Note: This can have false positives, use with caution.
Best Practices Summary
For Lending Protocols
✅ Use Chainlink or TWAP oracles, never spot prices
✅ Implement sanity checks on price changes
✅ Add circuit breakers for abnormal market conditions
✅ Require multi-block delays for large position changes
For DAOs
✅ Snapshot balances at proposal creation
✅ Add timelocks (minimum 24-48 hours)
✅ Require quorum from long-term holders
✅ Use vote delegation to prevent last-minute governance attacks
For All DeFi Protocols
✅ Audit all price dependencies
✅ Test with flash loan attack scenarios
✅ Monitor for unusual transaction patterns
✅ Have emergency pause mechanisms
✅ Maintain bug bounty programs
Conclusion
Flash loans are a powerful DeFi primitive that democratizes access to capital — but they also democratize access to exploits.
The attacks aren't caused by flash loans themselves, but by underlying vulnerabilities that flash loans amplify:
- Weak oracles (use TWAP or Chainlink)
- Instant governance (add snapshots and timelocks)
- Reentrancy (use guards)
- Unprotected liquidations (add delays)
The bottom line: If your protocol can be exploited with enough capital, assume someone will flash loan that capital.
Practice flash loan defense on Solingo — our exercises simulate real attacks including oracle manipulation and governance takeovers, with step-by-step defense implementation.