# audit-slippage

Audits Solidity DEX integrations and smart contracts for slippage vulnerabilities enabling sandwich attacks including zero/missing minAmountOut parameters, block.timestamp or missing deadlines, on-chain slippage calculation via manipulable quoters, multi-hop swaps without final output protection, decimal precision mismatches between token pairs, hard-coded slippage preventing withdrawals during volatility, and fixed fee tier assumptions breaking when liquidity migrates

- **Kind:** skill
- **Source:** https://github.com/auditmos/skills
- **Page:** https://forefy.com/skills/33362125-55ee-4719-9f24-f90f1d5d9692
- **API (JSON + files):** https://forefy.com/api/asr/33362125-55ee-4719-9f24-f90f1d5d9692

---

## SKILL.md

---
name: audit-slippage
description: Audits Solidity DEX integrations and smart contracts for slippage vulnerabilities enabling sandwich attacks including zero/missing minAmountOut parameters, block.timestamp or missing deadlines, on-chain slippage calculation via manipulable quoters, multi-hop swaps without final output protection, decimal precision mismatches between token pairs, hard-coded slippage preventing withdrawals during volatility, and fixed fee tier assumptions breaking when liquidity migrates
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"
---

# Slippage Protection Auditor

## When to Use
- Auditing DEX integrations, AMM interactions, swap operations
- User mentions: slippage, MEV, sandwich attack, front-running, deadline, minAmountOut, swap, Uniswap, Curve, Balancer
- Reviewing token exchange functions, liquidity operations, router integrations
- Analyzing price impact protection in DeFi protocols

## Audit Workflow

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

Begin with: "I'm using the **audit-slippage** skill to analyze this contract for slippage protection and MEV vulnerabilities..."

1. **Identify swap/liquidity operations**
   - Search: `swap`, `addLiquidity`, `removeLiquidity`, `IUniswapV2Router`, `ISwapRouter`
   - Focus: minAmountOut parameters, deadline parameters, quoter usage

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

3. **Validate MEV exploitability**
   - **Check access control first** - grep for `onlyOwner|onlyAdmin|onlyGovernance` modifiers
   - Can non-privileged actors exploit via MEV?
   - Can MEV bot sandwich attack?
   - Calculate extractable value (% of trade)
   - Check if protection exists elsewhere in call stack
   - Downgrade severity if admin-only unless users affected by MEV

4. **Generate report**
   - Use deliverable template below
   - Include sandwich attack PoC
   - Quantify MEV extraction potential

## Core Vulnerability Patterns

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

1. No slippage parameter (minAmountOut = 0) → 99%+ value extractable
2. No expiration deadline (type(uint256).max) → delayed execution risk
3. block.timestamp as deadline → zero protection
4. Incorrect slippage calculation → wrong reference value
5. Mismatched slippage precision → decimal scaling errors
6. Hard-coded slippage → withdrawal failures during volatility
7. MinTokensOut for intermediate amount → multi-hop unprotected
8. On-chain slippage calculation → flash loan manipulation
9. Fixed fee tier assumption → routing through wrong pool
10. Slippage on token amount not USD value → market crash risk
11. No slippage on liquidity ops → LP value extraction
12. Flash swap repayment without slippage → overpayment risk
13. Approval race on router upgrade → MEV via old router

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

## Severity Criteria

**Critical:** Zero slippage on user-facing swaps, missing deadline, on-chain quoter-based minOut, **MUST be exploitable by non-privileged actors**
**High:** Hard-coded slippage preventing withdrawals, intermediate-hop-only protection, wrong fee tier (80%+ liquidity elsewhere), **MUST be exploitable by non-privileged actors**
**Medium:** Wrong decimal precision (user can retry), suboptimal routing, missing LP operation slippage, **admin-only swap functions with cascading user MEV exposure**
**Low:** Suboptimal slippage (token vs USD) in stable pairs, documentation issues, **admin-only swap parameter issues without immediate user impact**

**IMPORTANT:** Admin-only swap functions (onlyOwner, onlyAdmin, onlyGovernance) are **MEDIUM or LOW severity** unless:
- Admin swaps use user funds directly (e.g., fee collection selling user-deposited tokens)
- Missing slippage enables admin to extract value from protocol treasury holding user funds
- Admin swap parameters affect user swap routing or slippage calculations

## False Positives - Do NOT Flag

- Zero slippage on internal protocol-to-protocol swaps (both sides controlled)
- block.timestamp deadline in keeper/bot functions with off-chain slippage enforcement
- Hard-coded slippage in emergency-only functions with explicit warnings
- On-chain quoter in view functions (display/estimation only)
- Fixed fee tier with documented single-pool targeting
- **Admin-only swap functions** (onlyOwner, onlyAdmin) swapping protocol-owned assets not derived from user funds
- Governance-controlled swaps with timelock allowing users to exit before execution
- Treasury management swaps where admin has no access to user deposits

## 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, impact (MEV extraction %), PoC with sandwich attack simulation, remediation, gas impact.

## Key Principles

- **User control** - users specify slippage and deadline per tx
- **Off-chain calculation** - minAmountOut from off-chain or TWAP, never current block
- **Final output protection** - multi-hop must protect final amount, not intermediate
- **Decimal awareness** - account for token decimal differences

## Output Guidelines

**DO:**
- Reference specific lines/functions
- Provide sandwich attack PoCs
- Quantify MEV extraction ($ or %)
- Include real-world exploit examples

**DON'T:**
- Flag view/pure functions (no state change)
- Report intentional designs without exploit path
- Use vague terms
- Ignore liquidity depth context

## checklist.md

* [ ] User can specify minTokensOut for all swaps
* [ ] User can specify deadline for time-sensitive operations
* [ ] Slippage calculated correctly (not modified)
* [ ] Slippage precision matches output token
* [ ] Hard-coded slippage can be overridden by users
* [ ] Slippage checked on final output amount
* [ ] Slippage calculated off-chain, not on-chain
* [ ] Fee tiers not hardcoded (allow multiple options)
* [ ] Proper deadline validation (not block.timestamp)

## example.md

# Code Examples: Slippage Protection Vulnerabilities

## Vulnerable Examples

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IUniswapV2Router {
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB, uint liquidity);
}

interface IQuoter {
    function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut);
}

contract VulnerableSlippageExamples {
    IUniswapV2Router public router;
    IQuoter public quoter;
    address[] public path;

    // Pattern #1: No Slippage Parameter (Hard-coded Zero)
    function swap_NoSlippage_VULNERABLE(uint256 amountIn) external {
        router.swapExactTokensForTokens(
            amountIn,
            0, // No slippage protection - catastrophic!
            path,
            msg.sender,
            block.timestamp + 300
        );
    }

    // Pattern #2: No Expiration Deadline
    function swap_NoDeadline_VULNERABLE(uint256 amountIn, uint256 minOut) external {
        router.swapExactTokensForTokens(
            amountIn,
            minOut,
            path,
            msg.sender,
            type(uint256).max // Infinite deadline - no protection
        );
    }

    // Pattern #3: Block.timestamp as Deadline
    function swap_BlockTimestampDeadline_VULNERABLE(uint256 amountIn, uint256 minOut) external {
        router.swapExactTokensForTokens(
            amountIn,
            minOut,
            path,
            msg.sender,
            block.timestamp // Always valid - useless
        );
    }

    // Pattern #4: Incorrect Slippage Calculation (Wrong Reference)
    function swap_WrongSlippageBase_VULNERABLE(uint256 amountIn, uint256 slippageBps) external {
        // Calculating slippage from input instead of expected output
        uint256 minOut = amountIn * (10000 - slippageBps) / 10000; // Wrong!

        router.swapExactTokensForTokens(
            amountIn,
            minOut,
            path,
            msg.sender,
            block.timestamp + 300
        );
    }

    // Pattern #5: Mismatched Slippage Precision
    function swap_DecimalMismatch_VULNERABLE(uint256 amountInUSDC) external {
        // amountIn is 1000 USDC (6 decimals) = 1000e6
        // Expected output: 0.95 WETH (18 decimals) = 0.95e18
        uint256 minOut = amountInUSDC * 95 / 100; // Results in 950e6, not 0.95e18!

        router.swapExactTokensForTokens(
            amountInUSDC,
            minOut,
            path,
            msg.sender,
            block.timestamp + 300
        );
    }

    // Pattern #6: Hard-coded Slippage
    uint256 constant SLIPPAGE_BPS = 500; // 5% hard-coded

    function withdraw_HardcodedSlippage_VULNERABLE(uint256 expectedOut) external {
        uint256 minOut = expectedOut * (10000 - SLIPPAGE_BPS) / 10000;

        router.swapExactTokensForTokens(
            100 ether,
            minOut,
            path,
            msg.sender,
            block.timestamp + 300
        );
        // Fails if market moves >5%
    }

    // Pattern #7: MinTokensOut For Intermediate Amount
    function multiHopSwap_VULNERABLE(uint256 amountA, uint256 minB) external {
        // Swap A -> B
        address[] memory pathAB = new address[](2);
        pathAB[0] = address(0x1);
        pathAB[1] = address(0x2);

        uint256[] memory amountsB = router.swapExactTokensForTokens(
            amountA,
            minB, // Protected intermediate
            pathAB,
            address(this),
            block.timestamp + 300
        );

        // Swap B -> C
        address[] memory pathBC = new address[](2);
        pathBC[0] = address(0x2);
        pathBC[1] = address(0x3);

        router.swapExactTokensForTokens(
            amountsB[1],
            0, // No protection on final output!
            pathBC,
            msg.sender,
            block.timestamp + 300
        );
    }

    // Pattern #8: On-Chain Slippage Calculation
    function swap_OnChainQuoter_VULNERABLE(uint256 amountIn, bytes memory quotePath) external {
        // Quoter uses current reserves - manipulable via flash loan!
        uint256 expectedOut = quoter.quoteExactInput(quotePath, amountIn);
        uint256 minOut = expectedOut * 95 / 100;

        router.swapExactTokensForTokens(
            amountIn,
            minOut, // Based on manipulable price
            path,
            msg.sender,
            block.timestamp + 300
        );
    }

    // Pattern #9: Fixed Fee Tier Assumption
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    function swapV3_FixedFeeTier_VULNERABLE(uint256 amountIn, uint256 minOut) external pure returns (ExactInputSingleParams memory) {
        return ExactInputSingleParams({
            tokenIn: address(0x1),
            tokenOut: address(0x2),
            fee: 3000, // Assumes 0.3% pool - might have better liquidity at 0.05%
            recipient: address(this),
            deadline: block.timestamp + 300,
            amountIn: amountIn,
            amountOutMinimum: minOut,
            sqrtPriceLimitX96: 0
        });
    }

    // Pattern #11: No Slippage on Liquidity Operations
    function addLiquidity_NoSlippage_VULNERABLE(
        address tokenA,
        address tokenB,
        uint256 amountA,
        uint256 amountB
    ) external {
        router.addLiquidity(
            tokenA,
            tokenB,
            amountA,
            amountB,
            0, // amountAMin - no protection
            0, // amountBMin - no protection
            msg.sender,
            block.timestamp + 300
        );
    }

    // Pattern #13: Approval Race on Router Upgrade
    address public oldRouter;

    function upgradeRouter_ApprovalRace_VULNERABLE(address newRouter, address token) external {
        router = IUniswapV2Router(newRouter);
        // Old router still has approval - can be exploited!
        // Missing: approve(oldRouter, 0)
    }
}
```

## Fixed Examples

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC20 {
    function approve(address spender, uint256 amount) external returns (bool);
    function decimals() external view returns (uint8);
}

interface IUniswapV2Router {
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB, uint liquidity);
}

interface IUniswapV3Pool {
    function liquidity() external view returns (uint128);
}

interface IUniswapV3Factory {
    function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool);
}

contract FixedSlippageExamples {
    IUniswapV2Router public router;
    IUniswapV3Factory public v3Factory;
    address[] public path;

    // Pattern #1 FIXED: User-Provided Slippage
    function swap_WithSlippage_FIXED(
        uint256 amountIn,
        uint256 minAmountOut,
        uint256 deadline
    ) external {
        require(minAmountOut > 0, "Invalid slippage");
        require(block.timestamp <= deadline, "Transaction expired");

        router.swapExactTokensForTokens(
            amountIn,
            minAmountOut, // User-provided protection
            path,
            msg.sender,
            deadline
        );
    }

    // Pattern #2 FIXED: User-Provided Deadline
    function swap_WithDeadline_FIXED(
        uint256 amountIn,
        uint256 minOut,
        uint256 deadline
    ) external {
        require(block.timestamp <= deadline, "Transaction expired");

        router.swapExactTokensForTokens(
            amountIn,
            minOut,
            path,
            msg.sender,
            deadline // User controls expiration
        );
    }

    // Pattern #3 FIXED: Accept Deadline Parameter
    function swap_ProperDeadline_FIXED(
        uint256 amountIn,
        uint256 minOut,
        uint256 deadline
    ) external {
        // Don't use block.timestamp - accept user deadline
        router.swapExactTokensForTokens(
            amountIn,
            minOut,
            path,
            msg.sender,
            deadline // From user, not block.timestamp
        );
    }

    // Pattern #4 FIXED: Calculate Slippage from Expected Output
    function swap_CorrectSlippageBase_FIXED(
        uint256 amountIn,
        uint256 expectedOut, // From off-chain quote
        uint256 slippageBps,
        uint256 deadline
    ) external {
        // Calculate from expected output, not input
        uint256 minOut = expectedOut * (10000 - slippageBps) / 10000;

        router.swapExactTokensForTokens(
            amountIn,
            minOut,
            path,
            msg.sender,
            deadline
        );
    }

    // Pattern #5 FIXED: Match Decimal Precision
    function swap_DecimalMatched_FIXED(
        uint256 amountInUSDC,
        uint256 expectedOutWETH, // Expected: 0.95e18
        uint256 slippageBps,
        uint256 deadline
    ) external {
        // expectedOutWETH is already in 18 decimals
        uint256 minOut = expectedOutWETH * (10000 - slippageBps) / 10000;
        // minOut preserves 18 decimals

        router.swapExactTokensForTokens(
            amountInUSDC,
            minOut,
            path,
            msg.sender,
            deadline
        );
    }

    // Pattern #6 FIXED: User-Controlled Slippage
    function withdraw_UserSlippage_FIXED(
        uint256 amount,
        uint256 expectedOut,
        uint256 slippageBps, // User specifies
        uint256 deadline
    ) external {
        uint256 minOut = expectedOut * (10000 - slippageBps) / 10000;

        router.swapExactTokensForTokens(
            amount,
            minOut,
            path,
            msg.sender,
            deadline
        );
    }

    // Pattern #7 FIXED: Protect Final Output in Multi-Hop
    function multiHopSwap_FIXED(
        uint256 amountA,
        uint256 minC, // Final output protection
        uint256 deadline
    ) external {
        // Use single multi-hop call with final slippage
        address[] memory pathAC = new address[](3);
        pathAC[0] = address(0x1); // A
        pathAC[1] = address(0x2); // B (intermediate)
        pathAC[2] = address(0x3); // C (final)

        router.swapExactTokensForTokens(
            amountA,
            minC, // Protects final output C
            pathAC,
            msg.sender,
            deadline
        );
    }

    // Pattern #8 FIXED: Off-Chain Slippage Calculation
    function swap_OffChainQuote_FIXED(
        uint256 amountIn,
        uint256 minAmountOut, // Calculated off-chain or via TWAP
        uint256 deadline
    ) external {
        // Accept pre-calculated minAmountOut from off-chain
        // or from TWAP oracle (not current block state)
        router.swapExactTokensForTokens(
            amountIn,
            minAmountOut,
            path,
            msg.sender,
            deadline
        );
    }

    // Pattern #9 FIXED: Query Multiple Fee Tiers
    function findBestFeeTier_FIXED(
        address tokenA,
        address tokenB
    ) public view returns (uint24 bestFee) {
        uint24[3] memory fees = [uint24(500), uint24(3000), uint24(10000)];
        uint128 maxLiquidity = 0;

        for (uint256 i = 0; i < fees.length; i++) {
            address pool = v3Factory.getPool(tokenA, tokenB, fees[i]);
            if (pool != address(0)) {
                uint128 liquidity = IUniswapV3Pool(pool).liquidity();
                if (liquidity > maxLiquidity) {
                    maxLiquidity = liquidity;
                    bestFee = fees[i];
                }
            }
        }

        require(maxLiquidity > 0, "No pool found");
        return bestFee;
    }

    // Pattern #11 FIXED: Slippage Protection on Liquidity Operations
    function addLiquidity_WithSlippage_FIXED(
        address tokenA,
        address tokenB,
        uint256 amountA,
        uint256 amountB,
        uint256 slippageBps,
        uint256 deadline
    ) external {
        uint256 amountAMin = amountA * (10000 - slippageBps) / 10000;
        uint256 amountBMin = amountB * (10000 - slippageBps) / 10000;

        router.addLiquidity(
            tokenA,
            tokenB,
            amountA,
            amountB,
            amountAMin, // Protected
            amountBMin, // Protected
            msg.sender,
            deadline
        );
    }

    // Pattern #13 FIXED: Revoke Old Approvals on Router Upgrade
    address public oldRouter;
    address public token;

    function upgradeRouter_Safe_FIXED(address newRouter) external {
        // Revoke old router approval first
        IERC20(token).approve(oldRouter, 0);

        // Update router
        oldRouter = address(router);
        router = IUniswapV2Router(newRouter);

        // Approve new router
        IERC20(token).approve(newRouter, type(uint256).max);
    }

    // Bonus: Emergency withdrawal with relaxed slippage
    bool public emergencyMode;

    function emergencyWithdraw_FIXED(
        uint256 amount,
        uint256 minOut,
        uint256 deadline
    ) external {
        require(emergencyMode, "Not emergency");
        // In emergency, use relaxed slippage (e.g., 20%)
        uint256 emergencyMinOut = minOut * 80 / 100;

        router.swapExactTokensForTokens(
            amount,
            emergencyMinOut,
            path,
            msg.sender,
            deadline
        );
    }

    // Bonus: Normalize decimals before slippage calculation
    function normalizeAmount_FIXED(
        address token,
        uint256 amount
    ) public view returns (uint256) {
        uint8 decimals = IERC20(token).decimals();
        if (decimals < 18) {
            return amount * (10 ** (18 - decimals));
        }
        return amount;
    }

    // Bonus: Helper for recommended slippage
    function getRecommendedSlippage_FIXED(
        address tokenA,
        address tokenB
    ) public pure returns (uint256 slippageBps) {
        // Stablecoin pairs: 0.5% (50 bps)
        // Volatile pairs: 2% (200 bps)
        // For demo - in production, query volatility metrics
        return 200; // 2% default for volatile pairs
    }
}
```

## reference.md

# Slippage Protection Vulnerabilities

1. **No Slippage Parameter** - Hard-coded 0 minimum output allows catastrophic MEV sandwich attacks
2. **No Expiration Deadline** - Transactions can be held and executed at unfavorable times
3. **Incorrect Slippage Calculation** - Using values other than minTokensOut for slippage protection
4. **Mismatched Slippage Precision** - Slippage not scaled to match output token decimals
5. **Hard-coded Slippage Freezes Funds** - Fixed slippage prevents withdrawals during high volatility
6. **MinTokensOut For Intermediate Amount** - Slippage only checked on intermediate, not final output
7. **On-Chain Slippage Calculation** - Using Quoter.quoteExactInput() subject to manipulation
8. **Fixed Fee Tier Assumption** - Hardcoding 3000 (0.3%) fee when pools may use different tiers
9. **Block.timestamp Deadline** - Using current timestamp provides no protection

## templates

```

```

## templates/report-template.md

# Slippage Protection Audit Report

**Contract:** [Contract Name]
**Files Analyzed:** [List of .sol files]
**Vulnerabilities Found:** Critical: X | High: Y | Medium: Z | Low: W

---

## [SEVERITY] Vulnerability Title
**Pattern:** #[1-13]
**File:** `path/to/Contract.sol`
**Lines:** [line numbers]
**Function:** `functionName()`

### Description
[2-3 sentences explaining the issue and what attack it enables]

### Vulnerable Code
```solidity
// Actual code from contract with line numbers
145: function swap(uint256 amountIn) external {
146:     router.swapExactTokensForTokens(
147:         amountIn,
148:         0, // No slippage protection
149:         path,
150:         msg.sender,
151:         block.timestamp // No deadline protection
152:     );
153: }
```

### Impact
- **MEV Extraction:** Up to [X]% of swap value extractable via sandwich attack
- **Exploitability:** [High/Medium/Low] - [Explanation of preconditions]
- **Daily Volume at Risk:** $[X] based on [source]
- **Affected Functions:** [List if multiple functions share this pattern]

### Proof of Concept
```solidity
// Sandwich attack simulation
function testSandwichAttack() public {
    // 1. Attacker front-runs with large buy
    vm.prank(attacker);
    uint256 attackBuyAmount = 1000 ether;
    router.swapExactTokensForTokens(attackBuyAmount, 0, path, attacker, block.timestamp);

    // 2. Victim transaction executes at inflated price (no slippage protection)
    vm.prank(victim);
    uint256 victimAmount = 10 ether;
    uint256 victimOut = router.swapExactTokensForTokens(victimAmount, 0, path, victim, block.timestamp);

    // 3. Attacker back-runs with sell
    vm.prank(attacker);
    router.swapExactTokensForTokens(attackBuyAmount, 0, reversePath, attacker, block.timestamp);

    // Demonstrate profit
    uint256 attackerProfit = attacker.balance - initialBalance;
    uint256 victimLoss = expectedOut - victimOut;

    // Attacker extracts ~40% of victim's intended output
    assertGt(attackerProfit, victimLoss * 35 / 100);
}
```

### Remediation
```solidity
// Fixed code
function swap(uint256 amountIn, uint256 minAmountOut, uint256 deadline) external {
    require(block.timestamp <= deadline, "Transaction expired");
    require(minAmountOut > 0, "Invalid slippage");

    router.swapExactTokensForTokens(
        amountIn,
        minAmountOut,
        path,
        msg.sender,
        deadline
    );
}
```

**Gas Impact:** +[X] gas per call (negligible vs security improvement)

---

## Summary

### Critical Issues Requiring Immediate Attention
1. [Issue #X] - [Brief description] - [Estimated MEV extraction potential]
2. [Issue #Y] - [Brief description] - [Estimated MEV extraction potential]

### Recommendations
- Accept user-provided slippage and deadline on all swap functions
- Calculate minAmountOut off-chain via front-end or keeper services
- Normalize all token amounts to common decimal base before slippage calculation
- For Uniswap V3, query multiple fee tiers and route through optimal pool
- Document recommended slippage values per asset pair (0.5% stables, 2% volatile)
- Implement emergency withdrawal mechanisms with relaxed slippage for black swan events
- Use TWAP oracles for on-chain price validation, never current block reserves
- Add circuit breakers that pause swaps during >50% single-block price moves

