Install in Claude Code
Copygit clone --depth 1 https://github.com/PurpleAILAB/Vigilo /tmp/flashloan && cp -r /tmp/flashloan/packages/opencode/skills/flashloan ~/.claude/skills/flashloanThen start a new Claude Code session; the skill loads automatically.
Definition
SKILL.md
# Flash Loan Attack Patterns
This skill provides comprehensive knowledge for identifying flash loan attack vulnerabilities in smart contracts.
## Overview
Flash loans provide unlimited capital for a single transaction. Any logic that depends on current state (balances, prices, voting power) without proper protection is potentially vulnerable.
## Why Flash Loan Attacks Happen (Root Causes)
### Root Cause 1: Atomic Transaction Assumption Violation
Developers assume state remains consistent within a transaction. Flash loans break this assumption by allowing unlimited capital injection and withdrawal within a single block.
```solidity
// VULNERABLE: Assumes balance is stable within transaction
function liquidate(address user) external {
uint256 collateral = collateralBalance[user]; // @audit Can be inflated
uint256 debt = debtBalance[user];
require(debt > collateral * LTV, "Healthy");
// Liquidate...
}
// Attacker's flow:
// 1. Flash loan 1M tokens
// 2. Deposit as collateral → collateralBalance[attacker] += 1M
// 3. Call liquidate(victim) at manipulated state
// 4. Withdraw collateral
// 5. Repay flash loan
```
**Attacker's view**: "I can temporarily inflate any balance-based metric within a single transaction. The contract assumes state is stable, but I control it."
### Root Cause 2: Balance-Based Logic Without Time Lock
Any function that reads balances (token, collateral, voting power) and makes decisions in the same transaction is vulnerable.
```solidity
// DANGEROUS: Balance read and action in same tx
function borrow(uint256 amount) external {
uint256 collateralValue = getCollateralValue(msg.sender); // @audit Flashloan-inflatable
require(amount <= collateralValue * LTV, "Insufficient collateral");
_borrow(amount);
}
function getCollateralValue(address user) public view returns (uint256) {
return token.balanceOf(address(this)) * userShare[user] / totalShares;
}
```
**Attacker's view**: "I deposit tokens, borrow against them, then withdraw. The contract never checks if I actually own the collateral after the transaction."
### Root Cause 3: Price Derivation from Manipulable Sources
Spot prices from DEXes can be manipulated within a single block via flash loans.
```solidity
// DANGEROUS: Spot price from AMM
function getPrice() public view returns (uint256) {
(uint112 reserve0, uint112 reserve1,) = pair.getReserves();
return reserve1 * 1e18 / reserve0; // @audit Manipulable!
}
function liquidate(address user) external {
uint256 price = getPrice(); // Attacker controls this
uint256 collateralValue = collateral[user] * price;
require(debt[user] > collateralValue, "Healthy");
// Liquidate at wrong price...
}
```
**Attacker's view**: "I flash loan one side of the pair, dump it to crash the price, then liquidate at the manipulated rate."
### Root Cause 4: Governance Without Historical Snapshots
Voting power based on current balance allows flash loan governance attacks.
```solidity
// DANGEROUS: Current balance voting
function castVote(uint256 proposalId, bool support) external {
uint256 votes = token.balanceOf(msg.sender); // @audit Flash loanable!
_recordVote(proposalId, msg.sender, votes, support);
}
```
**Attacker's view**: "I flash loan governance tokens, vote with them, then repay. The proposal passes with my temporary voting power."
---
## Attack Categories
### 1. Price Manipulation Attacks
**Root Cause**: Root Cause 3 (Price Derivation from Manipulable Sources)
**Vulnerable Pattern:**
```solidity
// DANGEROUS: Spot price from AMM
function getPrice() public view returns (uint256) {
(uint112 reserve0, uint112 reserve1,) = pair.getReserves();
return reserve1 * 1e18 / reserve0;
}
function liquidate(address user) external {
uint256 price = getPrice(); // Manipulable!
uint256 collateralValue = collateral[user] * price;
require(debt[user] > collateralValue, "Healthy");
// Liquidate...
}
```
**Attack Flow:**
```
1. Flash loan large amount of token0
2. Dump into AMM → reserve1/reserve0 crashes
3. Call liquidate() at manipulated price
4. Buy back token0 cheap
5. Repay flash loan
6. Profit from unfair liquidation
```
**Detection:**
```
Grep("getReserves|slot0\\(\\)|sqrtPriceX96", glob="**/*.sol")
```
---
### 2. Governance Manipulation
**Root Cause**: Root Cause 4 (Governance Without Historical Snapshots)
**Vulnerable Pattern:**
```solidity
// DANGEROUS: Balance-based voting
function castVote(uint256 proposalId, bool support) external {
uint256 votes = token.balanceOf(msg.sender); // Flash loanable!
_recordVote(proposalId, msg.sender, votes, support);
}
```
**Secure Pattern:**
```solidity
// Use snapshot at proposal creation
function castVote(uint256 proposalId, bool support) external {
uint256 snapshotBlock = proposals[proposalId].snapshotBlock;
uint256 votes = token.getPastVotes(msg.sender, snapshotBlock);
_recordVote(proposalId, msg.sender, votes, support);
}
```
**Detection:**
```
Grep("balanceOf.*vote|vote.*balanceOf", glob="**/*.sol")
Grep("propose|castVote|delegate", glob="**/*.sol")
```
---
### 3. Collateral Manipulation
**Root Cause**: Root Cause 2 (Balance-Based Logic Without Time Lock)
**Vulnerable Pattern:**
```solidity
// DANGEROUS: Instant collateral increase
function borrow(uint256 amount) external {
uint256 collateralValue = getCollateralValue(msg.sender);
require(amount <= collateralValue * LTV, "Insufficient collateral");
// Borrow...
}
function getCollateralValue(address user) public view returns (uint256) {
return token.balanceOf(address(this)) * userShare[user] / totalShares;
// ↑ Can be inflated with flash loan deposit
}
```
**Attack Flow:**
```
1. Flash loan tokens
2. Deposit as collateral → inflate collateralValue
3. Borrow maximum amount
4. Withdraw collateral
5. Repay flash loan (but keep borrowed funds)
```
---
### 4. Reward Manipulation
**Root Cause**: Root Cause 2 (Balance-Based Logic Without Ti