# audit-oracle

Audits Solidity oracle integrations for vulnerabilities including missing stale price checks against heartbeat intervals, missing L2 sequencer uptime validation, same heartbeat for multiple feeds, assuming oracle precision, incorrect price feed addresses, unhandled oracle reverts, unhandled depeg events, oracle min/max price issues during flash crashes, using manipulable slot0 prices, price feed direction confusion, and missing circuit breaker checks (project)

- **Kind:** skill
- **Source:** https://github.com/auditmos/skills
- **Page:** https://forefy.com/skills/2e070f5d-7ac1-4f92-919b-32c4fab86525
- **API (JSON + files):** https://forefy.com/api/asr/2e070f5d-7ac1-4f92-919b-32c4fab86525

---

## SKILL.md

---
name: audit-oracle
description: Audits Solidity oracle integrations for vulnerabilities including missing stale price checks against heartbeat intervals, missing L2 sequencer uptime validation, same heartbeat for multiple feeds, assuming oracle precision, incorrect price feed addresses, unhandled oracle reverts, unhandled depeg events, oracle min/max price issues during flash crashes, using manipulable slot0 prices, price feed direction confusion, and missing circuit breaker checks (project)
allowed-tools: Read, Grep, Glob
license: MIT
compatibility: Designed for Claude Code (or similar products)
metadata:
  author: Tomasz Kowalczyk (tom@auditmos.com)
  version: "1.0"
---

# Oracle Integration Auditor

## When to Use
- Auditing oracle integrations, price feeds, Chainlink usage
- User mentions: oracle, Chainlink, price feed, TWAP, stale price, heartbeat, sequencer, depeg, circuit breaker
- Analyzing price validation, oracle failure handling, feed configuration
- Reviewing Uniswap TWAP, Chainlink feeds, custom oracles

## Audit Workflow

**IMPORTANT: Announce skill usage at the start of analysis**

Begin with: "I'm using the **audit-oracle** skill to analyze this contract for oracle integration vulnerabilities..."

1. **Scan for oracle operations**
   - Search: `latestRoundData`, `getPrice`, `oracle`, `chainlink`, `feed`, `slot0`, `TWAP`, `observe`, `heartbeat`, `sequencer`
   - Focus: price fetching, staleness checks, error handling, feed addresses

2. **Check against vulnerability patterns**
   - Reference `reference.md` for complete checklist
   - Compare code against `example.md`

3. **Validate exploitability**
   - **Check access control first** - grep for `onlyOwner|onlyAdmin|onlyGovernance` modifiers
   - Can non-privileged actors exploit oracle issues?
   - Are stale prices checked with correct heartbeats?
   - Is L2 sequencer uptime verified on L2 deployments?
   - Can oracle failures cause DoS?
   - Are depeg scenarios handled?
   - Can slot0 be manipulated?
   - Verify no compensating protections exist
   - Downgrade severity if admin-only unless affects user pricing directly

4. **Generate report**
   - Use deliverable template below
   - Include price manipulation analysis and PoC
   - Rank by severity

## Core Vulnerability Patterns

See `reference.md` for full checklist. Key patterns:

1. Not checking stale prices → using outdated values during high volatility
2. Missing L2 sequencer check → prices during sequencer downtime
3. Same heartbeat for multiple feeds → wrong staleness thresholds
4. Assuming oracle precision → decimal mismatches causing errors
5. Incorrect price feed address → wrong asset pricing
6. Unhandled oracle reverts → complete DoS without fallback
7. Unhandled depeg events → using BTC/USD for compromised WBTC
8. Oracle min/max price issues → flash crashes return incorrect bounds
9. Using slot0 price → manipulable via flash loans
10. Price feed direction confusion → inverted pricing (DAI/USD vs USD/DAI)
11. Missing circuit breaker checks → not checking minAnswer/maxAnswer

**Code examples:** See `example.md`

## Severity Criteria

**Critical:** Using slot0 without TWAP enabling flash loan price manipulation, no staleness checks allowing stale price exploitation during high volatility, **MUST be exploitable by non-privileged actors**
**High:** Missing L2 sequencer checks on L2, unhandled oracle reverts causing DoS, depeg scenarios not handled, price direction confusion, **MUST be exploitable by non-privileged actors**
**Medium:** Incorrect heartbeat intervals, missing circuit breaker checks, assuming oracle decimals, incorrect feed addresses, **admin-only oracle configuration issues with cascading user impact**
**Low:** Suboptimal staleness thresholds, missing secondary oracle for redundancy, **admin-only parameter issues without immediate user impact**

**IMPORTANT:** Admin-only oracle functions (onlyOwner, onlyAdmin, onlyGovernance) are **MEDIUM or LOW severity** unless:
- Invalid oracle configuration directly prices user assets incorrectly
- Missing validation enables admin to rug pull via price manipulation
- Error cascades to affect all users immediately (e.g., stale prices in critical functions)

## False Positives - Do NOT Flag

- L1-only deployments (no sequencer concerns)
- Protocols with documented manual price updates
- Test environments with mock oracles
- View functions for display only (not used in logic)
- Oracles with explicit admin override mechanisms
- **Admin-only oracle setter functions** (onlyOwner, onlyAdmin) with minor validation issues
- Oracle feed updates by governance without immediate user pricing impact
- Parameter setters where admin is trusted and users can exit before changes take effect

## Deliverable Format

**MANDATORY:** Before deliverable, verify each `checklist.md` item against codebase. Flag violations as findings.

Use template: `templates/report-template.md`

Each finding includes: severity, pattern #, file/lines, description, vulnerable code, price manipulation analysis, PoC, remediation.

## Key Principles

- **Staleness validation** - check updatedAt against heartbeat for each feed
- **Failure handling** - wrap oracle calls in try/catch
- **L2 awareness** - check sequencer uptime on L2 chains
- **Depeg monitoring** - separate feeds for wrapped assets
- **Manipulation resistance** - use TWAP, not spot prices
- **Circuit breakers** - validate prices within bounds

## Output Guidelines

**DO:**
- Reference specific lines and functions
- Provide price manipulation scenarios
- Show PoCs with flash loan attacks or stale price exploitation
- List correct heartbeat intervals per feed
- Calculate impact of decimal mismatches

**DON'T:**
- Report missing features with alternative price sources
- Flag test/mock oracles in development
- Ignore chain-specific requirements (L2 sequencer)
- Miss multi-hop price calculations

## checklist.md

# Oracle Integration Security Checklist

Verify each item before finalizing audit report:

- [ ] **Stale price checks:** updatedAt validated against appropriate heartbeat for each feed
- [ ] **L2 sequencer check:** Sequencer uptime verified on L2 deployments before using prices
- [ ] **Feed-specific heartbeats:** Each feed uses its documented heartbeat interval (not generic timeout)
- [ ] **Oracle precision:** decimals() method used, no hardcoded decimal assumptions
- [ ] **Price feed addresses:** Verified correct for specific chain and asset
- [ ] **Oracle revert handling:** latestRoundData() calls wrapped in try/catch with fallback
- [ ] **Depeg monitoring:** Wrapped assets have separate feeds or depeg detection (e.g., WBTC/BTC)
- [ ] **Min/max validation:** Prices checked against circuit breaker bounds (minAnswer/maxAnswer)
- [ ] **TWAP usage:** Time-weighted average used instead of spot prices where appropriate
- [ ] **Price direction:** Quote/base token order verified correct (not inverted)
- [ ] **Circuit breaker checks:** Returned price validated not exactly at min/max bounds

## example.md

# Oracle Integration Vulnerability Examples

## Pattern #1: Not Checking Stale Prices

### VULNERABLE
```solidity
contract VulnerableStalePrice {
    AggregatorV3Interface internal priceFeed;

    function getPrice() public view returns (uint256) {
        // ISSUE: No staleness check on updatedAt
        (, int256 price, , , ) = priceFeed.latestRoundData();

        // During oracle outage or high volatility:
        // - updatedAt could be hours old
        // - price no longer reflects market
        // - enables arbitrage and unfair liquidations

        return uint256(price);
    }
}
```

### FIXED
```solidity
contract FixedStalePrice {
    AggregatorV3Interface internal priceFeed;
    uint256 public constant HEARTBEAT = 3600; // 1 hour for ETH/USD

    function getPrice() public view returns (uint256) {
        (, int256 price, , uint256 updatedAt, ) = priceFeed.latestRoundData();

        // Verify price is fresh
        require(
            block.timestamp - updatedAt <= HEARTBEAT,
            "Stale price"
        );

        return uint256(price);
    }
}
```

## Pattern #2: Missing L2 Sequencer Check

### VULNERABLE
```solidity
contract VulnerableL2Oracle {
    AggregatorV3Interface internal priceFeed;

    // ISSUE: On Arbitrum/Optimism, no sequencer check
    function getCollateralValue(address user) public view returns (uint256) {
        (, int256 price, , , ) = priceFeed.latestRoundData();

        // During sequencer downtime:
        // - Oracle updates stop
        // - Prices become stale
        // - Users liquidated unfairly when sequencer restarts

        return userCollateral[user] * uint256(price) / 1e8;
    }
}
```

### FIXED
```solidity
contract FixedL2Oracle {
    AggregatorV3Interface internal priceFeed;
    AggregatorV3Interface internal sequencerUptimeFeed;
    uint256 public constant GRACE_PERIOD = 3600; // 1 hour

    function getCollateralValue(address user) public view returns (uint256) {
        // Check sequencer status
        (, int256 answer, uint256 startedAt, , ) = sequencerUptimeFeed.latestRoundData();

        // 0 = sequencer up, 1 = sequencer down
        require(answer == 0, "Sequencer down");

        // Grace period after sequencer restart
        require(
            block.timestamp - startedAt > GRACE_PERIOD,
            "Grace period active"
        );

        // Now safe to use price
        (, int256 price, , uint256 updatedAt, ) = priceFeed.latestRoundData();
        require(block.timestamp - updatedAt <= 3600, "Stale price");

        return userCollateral[user] * uint256(price) / 1e8;
    }
}
```

## Pattern #3: Same Heartbeat For Multiple Feeds

### VULNERABLE
```solidity
contract VulnerableSameHeartbeat {
    AggregatorV3Interface internal btcFeed;
    AggregatorV3Interface internal usdcFeed;
    uint256 public constant HEARTBEAT = 3600; // 1 hour - WRONG for USDC

    function getPrices() public view returns (uint256 btc, uint256 usdc) {
        // ISSUE: BTC feed has 1h heartbeat, but USDC has 24h heartbeat
        (, int256 btcPrice, , uint256 btcUpdated, ) = btcFeed.latestRoundData();
        (, int256 usdcPrice, , uint256 usdcUpdated, ) = usdcFeed.latestRoundData();

        // USDC check will fail incorrectly - 24h is normal for stablecoins
        require(block.timestamp - btcUpdated <= HEARTBEAT, "BTC stale");
        require(block.timestamp - usdcUpdated <= HEARTBEAT, "USDC stale"); // FALSE POSITIVE

        return (uint256(btcPrice), uint256(usdcPrice));
    }
}
```

### FIXED
```solidity
contract FixedPerFeedHeartbeat {
    AggregatorV3Interface internal btcFeed;
    AggregatorV3Interface internal usdcFeed;

    // Feed-specific heartbeats from Chainlink docs
    uint256 public constant BTC_HEARTBEAT = 3600; // 1 hour
    uint256 public constant USDC_HEARTBEAT = 86400; // 24 hours

    function getPrices() public view returns (uint256 btc, uint256 usdc) {
        (, int256 btcPrice, , uint256 btcUpdated, ) = btcFeed.latestRoundData();
        (, int256 usdcPrice, , uint256 usdcUpdated, ) = usdcFeed.latestRoundData();

        require(block.timestamp - btcUpdated <= BTC_HEARTBEAT, "BTC stale");
        require(block.timestamp - usdcUpdated <= USDC_HEARTBEAT, "USDC stale");

        return (uint256(btcPrice), uint256(usdcPrice));
    }
}
```

## Pattern #4: Assuming Oracle Precision

### VULNERABLE
```solidity
contract VulnerableAssumedDecimals {
    AggregatorV3Interface internal priceFeed;

    function getCollateralValue(uint256 collateralAmount) public view returns (uint256) {
        (, int256 price, , , ) = priceFeed.latestRoundData();

        // ISSUE: Assumes 18 decimals but Chainlink feeds return 8
        // collateralAmount is in 18 decimals
        // price is in 8 decimals
        // Result off by 10^10

        return collateralAmount * uint256(price); // WRONG
    }
}
```

### FIXED
```solidity
contract FixedOracleDecimals {
    AggregatorV3Interface internal priceFeed;

    function getCollateralValue(uint256 collateralAmount) public view returns (uint256) {
        (, int256 price, , , ) = priceFeed.latestRoundData();
        uint8 decimals = priceFeed.decimals(); // Usually 8

        // Scale price to 18 decimals to match collateralAmount
        uint256 scaledPrice = uint256(price) * 10 ** (18 - decimals);

        return collateralAmount * scaledPrice / 1e18;
    }
}
```

## Pattern #6: Unhandled Oracle Reverts

### VULNERABLE
```solidity
contract VulnerableOracleRevert {
    AggregatorV3Interface internal priceFeed;

    function liquidate(address user) external {
        // ISSUE: If oracle reverts, entire protocol DoS
        (, int256 price, , , ) = priceFeed.latestRoundData();

        uint256 collateralValue = getCollateral(user) * uint256(price) / 1e8;

        // Oracle maintenance/failure = complete protocol freeze
        // Nobody can liquidate, withdraw, or perform any action
    }
}
```

### FIXED
```solidity
contract FixedOracleRevert {
    AggregatorV3Interface internal priceFeed;
    AggregatorV3Interface internal backupFeed;
    uint256 public lastKnownPrice;

    function getPrice() public returns (uint256) {
        try priceFeed.latestRoundData() returns (
            uint80,
            int256 price,
            uint256,
            uint256 updatedAt,
            uint80
        ) {
            require(block.timestamp - updatedAt <= 3600, "Stale");
            lastKnownPrice = uint256(price);
            return uint256(price);
        } catch {
            // Try backup oracle
            try backupFeed.latestRoundData() returns (
                uint80,
                int256 backupPrice,
                uint256,
                uint256 backupUpdated,
                uint80
            ) {
                require(block.timestamp - backupUpdated <= 3600, "Backup stale");
                lastKnownPrice = uint256(backupPrice);
                return uint256(backupPrice);
            } catch {
                // Use last known price with warning
                require(lastKnownPrice > 0, "No price available");
                return lastKnownPrice;
            }
        }
    }
}
```

## Pattern #7: Unhandled Depeg Events

### VULNERABLE
```solidity
contract VulnerableDepeg {
    AggregatorV3Interface internal btcFeed; // BTC/USD feed

    function getWBTCValue(uint256 wbtcAmount) public view returns (uint256) {
        // ISSUE: Uses BTC/USD for WBTC without checking peg
        (, int256 btcPrice, , , ) = btcFeed.latestRoundData();

        // If WBTC bridge compromised and depegs:
        // - WBTC trades at $10k but BTC at $60k
        // - Oracle shows $60k for worthless WBTC
        // - Protocol becomes insolvent

        return wbtcAmount * uint256(btcPrice) / 1e8;
    }
}
```

### FIXED
```solidity
contract FixedDepeg {
    AggregatorV3Interface internal btcFeed; // BTC/USD
    AggregatorV3Interface internal wbtcBtcFeed; // WBTC/BTC peg feed
    uint256 public constant MIN_PEG = 0.98e8; // 98% - 2% depeg tolerance

    function getWBTCValue(uint256 wbtcAmount) public view returns (uint256) {
        // Get BTC price
        (, int256 btcPrice, , uint256 btcUpdated, ) = btcFeed.latestRoundData();
        require(block.timestamp - btcUpdated <= 3600, "BTC price stale");

        // Check WBTC peg to BTC
        (, int256 pegPrice, , uint256 pegUpdated, ) = wbtcBtcFeed.latestRoundData();
        require(block.timestamp - pegUpdated <= 3600, "Peg price stale");

        // Verify peg is maintained
        require(pegPrice >= int256(MIN_PEG), "WBTC depegged");

        // Use lower of: BTC price or WBTC/BTC ratio * BTC price
        uint256 wbtcPrice = uint256(btcPrice) * uint256(pegPrice) / 1e8;

        return wbtcAmount * wbtcPrice / 1e8;
    }
}
```

## Pattern #9: Using Slot0 Price

### VULNERABLE
```solidity
contract VulnerableSlot0 {
    IUniswapV3Pool public pool;

    function getPrice() public view returns (uint256) {
        // ISSUE: slot0 is spot price, manipulable in single tx
        (uint160 sqrtPriceX96, , , , , , ) = pool.slot0();

        // Flash loan attack:
        // 1. Borrow large amount
        // 2. Swap to manipulate pool price
        // 3. Call getPrice() - returns manipulated value
        // 4. Exploit overvalued collateral
        // 5. Repay flash loan
        // All in one transaction!

        uint256 price = (uint256(sqrtPriceX96) * uint256(sqrtPriceX96) * 1e18) >> 192;
        return price;
    }
}
```

### FIXED
```solidity
contract FixedTWAP {
    IUniswapV3Pool public pool;
    uint32 public constant TWAP_INTERVAL = 1800; // 30 minutes

    function getPrice() public view returns (uint256) {
        // Use TWAP instead of spot price
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = TWAP_INTERVAL;
        secondsAgos[1] = 0;

        (int56[] memory tickCumulatives, ) = pool.observe(secondsAgos);

        // Calculate time-weighted average tick
        int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
        int24 arithmeticMeanTick = int24(tickCumulativesDelta / int56(uint56(TWAP_INTERVAL)));

        // Convert tick to price
        uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(arithmeticMeanTick);
        uint256 price = (uint256(sqrtPriceX96) * uint256(sqrtPriceX96) * 1e18) >> 192;

        // TWAP cannot be manipulated in single transaction
        return price;
    }
}
```

## Pattern #10: Price Feed Direction Confusion

### VULNERABLE
```solidity
contract VulnerablePriceDirection {
    AggregatorV3Interface internal daiFeed; // DAI/USD feed

    function getDaiPerEth() public view returns (uint256) {
        // ISSUE: Need ETH/DAI but using DAI/USD
        (, int256 daiPrice, , , ) = daiFeed.latestRoundData(); // $1.00

        // daiPrice = 1.00 (DAI per USD)
        // Need: DAI per ETH
        // Should be: ETH/USD ÷ DAI/USD = 3000 / 1 = 3000 DAI per ETH
        // Code returns: 1 DAI per ETH - WRONG by 3000x

        return uint256(daiPrice);
    }
}
```

### FIXED
```solidity
contract FixedPriceDirection {
    AggregatorV3Interface internal ethFeed; // ETH/USD
    AggregatorV3Interface internal daiFeed; // DAI/USD

    function getDaiPerEth() public view returns (uint256) {
        (, int256 ethPrice, , , ) = ethFeed.latestRoundData(); // $3000
        (, int256 daiPrice, , , ) = daiFeed.latestRoundData(); // $1.00

        uint8 ethDecimals = ethFeed.decimals(); // 8
        uint8 daiDecimals = daiFeed.decimals(); // 8

        // DAI per ETH = (ETH/USD) / (DAI/USD)
        // = 3000 / 1 = 3000 DAI per ETH
        uint256 daiPerEth = (uint256(ethPrice) * 10 ** daiDecimals) / uint256(daiPrice);

        return daiPerEth;
    }
}
```

## Pattern #11: Missing Circuit Breaker Checks

### VULNERABLE
```solidity
contract VulnerableCircuitBreaker {
    AggregatorV3Interface internal priceFeed;

    function getPrice() public view returns (uint256) {
        (, int256 price, , , ) = priceFeed.latestRoundData();

        // ISSUE: During flash crash, Chainlink returns minAnswer/maxAnswer
        // E.g., ETH flash crashes to $1, but oracle has minAnswer = $100
        // Oracle returns $100 (circuit breaker), not $1
        // Protocol treats $100 as real price - incorrect collateral valuation

        return uint256(price);
    }
}
```

### FIXED
```solidity
contract FixedCircuitBreaker {
    AggregatorV3Interface internal priceFeed;

    function getPrice() public view returns (uint256) {
        AggregatorV2V3Interface aggregator = AggregatorV2V3Interface(address(priceFeed));

        (, int256 price, , , ) = priceFeed.latestRoundData();

        // Get circuit breaker bounds
        int192 minAnswer = aggregator.minAnswer();
        int192 maxAnswer = aggregator.maxAnswer();

        // Verify price not at bounds
        require(
            price > minAnswer && price < maxAnswer,
            "Circuit breaker triggered"
        );

        // During extreme events, revert rather than use bound value
        // Forces manual intervention or fallback pricing

        return uint256(price);
    }
}
```

## Summary: Key Protections

1. **Staleness checks:** updatedAt vs feed-specific heartbeat
2. **L2 sequencer:** Check uptime + grace period on Arbitrum/Optimism
3. **Decimals:** Call decimals(), never assume 8 or 18
4. **Error handling:** try/catch with fallback oracle
5. **Depeg monitoring:** Separate feeds for wrapped assets
6. **TWAP:** Use time-weighted average, not slot0 spot price
7. **Direction:** Verify quote/base order, calculate correctly
8. **Circuit breakers:** Check price != minAnswer/maxAnswer

## Feed-Specific Heartbeats (Ethereum Mainnet)

- ETH/USD: 3600s (1 hour)
- BTC/USD: 3600s (1 hour)
- USDC/USD: 86400s (24 hours)
- USDT/USD: 86400s (24 hours)
- DAI/USD: 3600s (1 hour)
- LINK/USD: 3600s (1 hour)

Always verify current values in Chainlink docs.

## reference.md

# Oracle Integration Vulnerability Patterns

## Pattern #1: Not Checking Stale Prices
**Risk:** Missing updatedAt validation against heartbeat intervals allows using outdated prices during market volatility or oracle failures
**Detection:** Verify updatedAt from latestRoundData() compared against feed-specific heartbeat (not generic timeout)
**Impact:** Protocol uses stale prices for liquidations, collateral valuation, swaps - enabling arbitrage and unfair liquidations

## Pattern #2: Missing L2 Sequencer Check
**Risk:** L2 chains (Arbitrum, Optimism) require sequencer uptime validation - prices during downtime are stale
**Detection:** Check for sequencer uptime feed integration on L2 deployments
**Impact:** Stale prices used after sequencer restart, mass unfair liquidations, price manipulation during downtime

## Pattern #3: Same Heartbeat For Multiple Feeds
**Risk:** Different Chainlink feeds have different heartbeat intervals (BTC: 1h, ETH: 1h, stablecoins: 24h)
**Detection:** Verify each feed uses its specific documented heartbeat, not hardcoded single value
**Impact:** False staleness rejections or accepting actually stale prices

## Pattern #4: Assuming Oracle Precision
**Risk:** Different feeds return different decimals (most: 8, some: 18) - hardcoding decimals causes errors
**Detection:** Check if code calls decimals() method or hardcodes decimal assumption
**Impact:** Prices off by 10^10, catastrophic miscalculation of collateral/debt values

## Pattern #5: Incorrect Price Feed Address
**Risk:** Using wrong feed address (mainnet address on testnet, ETH/USD instead of WETH/USD)
**Detection:** Verify feed addresses match Chainlink documentation for specific chain and asset
**Impact:** Completely wrong prices, protocol insolvency

## Pattern #6: Unhandled Oracle Reverts
**Risk:** Oracle calls not wrapped in try/catch - any oracle failure causes complete protocol DoS
**Detection:** Check if latestRoundData() wrapped in error handling
**Impact:** Protocol becomes unusable during oracle maintenance or failures

## Pattern #7: Unhandled Depeg Events
**Risk:** Using BTC/USD for WBTC ignores bridge compromise scenarios where WBTC depegs from BTC
**Detection:** Verify wrapped assets have separate feeds or depeg monitoring (WBTC/BTC feed)
**Impact:** Catastrophic losses when wrapped asset loses peg, collateral becomes worthless while oracle shows full value

## Pattern #8: Oracle Min/Max Price Issues
**Risk:** Chainlink has minAnswer/maxAnswer bounds - during flash crashes oracle returns bound value, not actual price
**Detection:** Check if code validates price isn't exactly at circuit breaker bounds
**Impact:** Protocol uses incorrect price during extreme volatility, enables liquidation manipulation

## Pattern #9: Using Slot0 Price
**Risk:** Uniswap V3 slot0 price is spot price, manipulable via flash loans in single transaction
**Detection:** Check if code reads slot0 directly without TWAP
**Impact:** Flash loan price manipulation, steal funds via manipulated collateral valuation

## Pattern #10: Price Feed Direction Confusion
**Risk:** Using opposite token pair direction (DAI/USD when needing USD/DAI) results in inverted pricing
**Detection:** Verify quote/base token order matches protocol needs
**Impact:** Inverted prices, collateral overvalued or undervalued by square of error

## Pattern #11: Missing Circuit Breaker Checks
**Risk:** Not checking if returned price equals minAnswer/maxAnswer bounds during extreme events
**Detection:** Verify code checks price != minAnswer && price != maxAnswer
**Impact:** Protocol uses circuit breaker bound values as real prices during flash crashes

## templates

```

```

## templates/report-template.md

# Oracle Integration Security Audit Report

## Executive Summary

**Contract:** [Contract Name]
**Audit Date:** [Date]
**Auditor:** [Name/Team]

**Findings Overview:**
- Critical: X
- High: X
- Medium: X
- Low: X

## Findings

---

### [SEVERITY] Finding #X: [Vulnerability Title]

**Pattern:** #X - [Pattern Name from reference.md]

**Location:** `[contract_name.sol:line_numbers]`

**Description:**

[Detailed explanation of the oracle integration vulnerability]

**Vulnerable Code:**

```solidity
function getPrice() public view returns (uint256) {
    (, int256 price, , , ) = priceFeed.latestRoundData();
    // Missing: staleness check, error handling, etc.
    return uint256(price);
}
```

**Price Manipulation Analysis:**

[Analyze exploitation scenario:]
- **Attack vector:** [Flash loan, stale price, depeg, etc.]
- **Price impact:** [How attacker manipulates or exploits price]
- **Profit mechanism:** [How attacker extracts value]
- **Cost of attack:** [Flash loan fee, gas, etc.]

**Proof of Concept:**

```solidity
contract OracleExploit {
    VulnerableContract target;
    IUniswapV3Pool pool;

    function attack() external {
        // 1. Setup: Flash loan large amount
        // 2. Manipulate: Swap to move price/use stale data
        // 3. Exploit: Call target with manipulated price
        // 4. Extract: Profit from mispriced collateral
        // 5. Repay: Flash loan
    }
}
```

**Scenario:**
1. [Oracle state - e.g., "Last update 3 hours ago during high volatility"]
2. [Market event - e.g., "ETH price dropped 10% but oracle shows old price"]
3. [Attacker action - e.g., "Deposits collateral valued at stale high price"]
4. [Exploitation - e.g., "Borrows maximum against overvalued collateral"]
5. [Result - e.g., "Protocol loses $X when actual collateral worth less"]

**Impact Analysis:**

**Direct Impact:**
- [Price manipulation impact - e.g., "Collateral overvalued by $X"]
- [Liquidation impact - e.g., "Unfair liquidations during stale price window"]

**Systemic Impact:**
- [Protocol-wide effect - e.g., "All positions mispriced during oracle outage"]
- [Cascading effects - e.g., "Liquidation cascade from wrong prices"]

**Affected Operations:**
- [List functions using oracle - e.g., "liquidate(), borrow(), getCollateralValue()"]

**Remediation:**

```solidity
function getPrice() public view returns (uint256) {
    (, int256 price, , uint256 updatedAt, ) = priceFeed.latestRoundData();

    // Add staleness check
    require(
        block.timestamp - updatedAt <= HEARTBEAT,
        "Stale price"
    );

    // Add circuit breaker check
    require(
        price > minAnswer && price < maxAnswer,
        "Circuit breaker triggered"
    );

    return uint256(price);
}
```

**Recommendations:**
1. [Primary fix - e.g., "Add staleness validation with correct heartbeat"]
2. [Secondary fix - e.g., "Wrap oracle calls in try/catch"]
3. [Defense in depth - e.g., "Add backup oracle or circuit breakers"]

**Gas Impact:** [Estimated additional gas cost for fix]
- Staleness check: ~100 gas
- Try/catch: ~2,000 gas

---

### [SEVERITY] Finding #X: [Next Vulnerability]

[Repeat above structure for each finding]

---

## Severity Definitions

**Critical:** Using slot0 without TWAP enabling flash loan manipulation, no staleness checks allowing exploitation during high volatility, missing L2 sequencer checks on L2 enabling mass liquidations.

**High:** Unhandled oracle reverts causing protocol DoS, depeg scenarios not monitored for wrapped assets, price direction confusion causing inverted pricing, incorrect feed addresses.

**Medium:** Incorrect heartbeat intervals for feeds, missing circuit breaker checks, hardcoded decimal assumptions, missing backup oracle.

**Low:** Suboptimal staleness thresholds, missing secondary oracle for redundancy without impact.

## Recommendations Summary

### Immediate Actions (Critical/High)
1. [List critical fixes]
   - Example: "Add TWAP instead of slot0 for Uniswap V3 prices"
   - Example: "Implement staleness checks with feed-specific heartbeats"
   - Example: "Add L2 sequencer uptime validation on Arbitrum"

### Short-term Improvements (Medium)
1. [List medium-priority enhancements]
   - Example: "Add circuit breaker validation (minAnswer/maxAnswer)"
   - Example: "Implement try/catch for oracle calls"
   - Example: "Add WBTC/BTC depeg monitoring"

### Long-term Enhancements (Low)
1. [List optimization opportunities]
   - Example: "Deploy backup oracle system"
   - Example: "Implement multi-oracle aggregation"

## Checklist Results

Based on `checklist.md`:

- [x] **Stale price checks:** updatedAt validated against heartbeat ✓/✗
- [x] **L2 sequencer check:** Sequencer uptime verified on L2 ✓/✗
- [x] **Feed-specific heartbeats:** Each feed uses correct interval ✓/✗
- [x] **Oracle precision:** decimals() method used ✓/✗
- [x] **Price feed addresses:** Verified correct for chain/asset ✓/✗
- [x] **Oracle revert handling:** Wrapped in try/catch ✓/✗
- [x] **Depeg monitoring:** Wrapped assets monitored ✓/✗
- [x] **Min/max validation:** Circuit breaker bounds checked ✓/✗
- [x] **TWAP usage:** Time-weighted average used ✓/✗
- [x] **Price direction:** Quote/base order correct ✓/✗
- [x] **Circuit breaker checks:** Price not at bounds ✓/✗

## Oracle Configuration Analysis

### Price Feeds Used

| Asset | Feed Address | Heartbeat | Decimals | Staleness Check? |
|-------|-------------|-----------|----------|-----------------|
| ETH/USD | 0x... | 3600s | 8 | No ❌ |
| BTC/USD | 0x... | 3600s | 8 | No ❌ |
| USDC/USD | 0x... | 86400s | 8 | Yes ✓ |

### Feed Address Verification

- [ ] All feeds match Chainlink documentation for current chain
- [ ] No mainnet addresses on testnet
- [ ] No deprecated feeds used
- [ ] Asset pairs correct (WETH not ETH, etc.)

### L2 Deployment Considerations

**Chain:** [Ethereum/Arbitrum/Optimism/etc.]

If L2:
- [ ] Sequencer uptime feed integrated
- [ ] Grace period implemented after sequencer restart
- [ ] Feeds verified for L2 chain (not mainnet addresses)

## Price Manipulation Scenarios

### Flash Loan Attack (Slot0)

```
1. Attacker flash borrows 10,000 ETH
2. Swaps to move Uniswap pool price 50%
3. Calls protocol function reading slot0
4. Protocol values collateral at manipulated 50% higher price
5. Attacker borrows max against overvalued collateral
6. Swaps back, repays flash loan
7. Profit: Borrowed funds - flash loan fee
8. Protocol left with undercollateralized loan
```

### Stale Price Exploitation

```
1. Oracle last updated 2 hours ago: ETH = $3000
2. Market crashes: actual ETH = $2500
3. Attacker deposits $2500 worth ETH
4. Protocol values at stale $3000 (+20% error)
5. Attacker borrows $2400 (80% LTV of $3000)
6. Actually: $2400 borrowed against $2500 (96% LTV)
7. Small price movement triggers liquidation
8. Or: Attacker profits from $100 overvaluation
```

### Depeg Exploitation

```
1. WBTC bridge compromised, WBTC depegs
2. WBTC market value: $10,000
3. Oracle uses BTC/USD: $60,000
4. Attacker buys cheap WBTC at $10k
5. Deposits into protocol valued at $60k
6. Borrows $48k (80% LTV)
7. Walks away with $38k profit per WBTC
8. Protocol holds worthless collateral
```

## Testing Recommendations

### Unit Tests
- [ ] Staleness check enforcement for each feed
- [ ] L2 sequencer grace period validation
- [ ] Oracle revert handling (mock revert)
- [ ] Circuit breaker detection
- [ ] Decimal scaling correctness
- [ ] Price direction calculations

### Integration Tests
- [ ] Multi-feed price calculations
- [ ] Fallback oracle activation
- [ ] Depeg scenario handling
- [ ] Flash loan price manipulation prevention

### Scenario Tests
- [ ] High volatility with delayed oracle updates
- [ ] Oracle outage with fallback
- [ ] L2 sequencer downtime + restart
- [ ] Circuit breaker activation
- [ ] Depeg event handling

## Appendix

### Chainlink Feed Addresses (Ethereum Mainnet)

- ETH/USD: `0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419`
- BTC/USD: `0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c`
- USDC/USD: `0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6`
- DAI/USD: `0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9`

### L2 Sequencer Uptime Feeds

- Arbitrum: `0xFdB631F5EE196F0ed6FAa767959853A9F217697D`
- Optimism: `0x371EAD81c9102C9BF4874A9075FFFf170F2Ee389`

### Heartbeat Intervals Reference

| Feed | Ethereum | Arbitrum | Optimism |
|------|----------|----------|----------|
| ETH/USD | 3600s | 86400s | 1200s |
| BTC/USD | 3600s | 86400s | 1200s |
| USDC/USD | 86400s | 86400s | 86400s |

### Circuit Breaker Bounds Example

ETH/USD feed (Ethereum):
- minAnswer: `1e6` ($0.01 with 8 decimals)
- maxAnswer: `1e15` ($10,000,000 with 8 decimals)

During flash crash below $0.01 or spike above $10M, oracle returns bound value - not actual price.

### TWAP Implementation Reference

```solidity
// Uniswap V3 TWAP - safe against manipulation
function getTWAP(IUniswapV3Pool pool, uint32 interval) internal view returns (uint256) {
    uint32[] memory secondsAgos = new uint32[](2);
    secondsAgos[0] = interval;
    secondsAgos[1] = 0;

    (int56[] memory tickCumulatives, ) = pool.observe(secondsAgos);
    int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
    int24 arithmeticMeanTick = int24(tickCumulativesDelta / int56(uint56(interval)));

    return OracleLibrary.getQuoteAtTick(
        arithmeticMeanTick,
        uint128(1e18),
        token0,
        token1
    );
}
```

### Depeg Detection Example

```solidity
// Monitor WBTC peg to BTC
function isWBTCPegged() public view returns (bool) {
    AggregatorV3Interface wbtcBtcFeed = AggregatorV3Interface(
        0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23 // WBTC/BTC
    );

    (, int256 pegPrice, , uint256 updatedAt, ) = wbtcBtcFeed.latestRoundData();

    require(block.timestamp - updatedAt <= 3600, "Peg feed stale");

    // Peg ratio should be near 1.0 (1e8 with 8 decimals)
    // Allow 2% deviation
    return pegPrice >= 0.98e8 && pegPrice <= 1.02e8;
}
```

