High severity

AMM spot price used as an oracle (price manipulation)

The contract reads the instantaneous state of an AMM pool (Uniswap V2 style getReserves() or Uniswap V3 style slot0()) and can use it as a price. The spot price of a pool can be moved arbitrarily within a single transaction using a flash loan or a large swap, so any valuation, collateral check, mint/redeem ratio or liquidation threshold derived from it can be manipulated and the protocol drained. This is one of the most frequent root causes of DeFi exploits and of high-severity audit-contest findings. Confidence is low because reading reserves is legitimate for quoting swaps that are themselves slippage-protected.

Vulnerable pattern

A minimal contract with the bug
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IUniswapV2Pair {
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
}

interface IUniswapV3Pool {
    function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex,
        uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked);
}

contract Lending {
    IUniswapV2Pair public pair;
    IUniswapV3Pool public pool;
    mapping(address => uint256) public collateral;

    constructor(IUniswapV2Pair _pair, IUniswapV3Pool _pool) {
        pair = _pair;
        pool = _pool;
    }

    function collateralPrice() public view returns (uint256) {
        (uint112 r0, uint112 r1, ) = pair.getReserves();
        return uint256(r1) * 1e18 / uint256(r0);
    }

    function poolPrice() public view returns (uint256) {
        (uint160 sqrtPriceX96, , , , , , ) = pool.slot0();
        return uint256(sqrtPriceX96) * uint256(sqrtPriceX96) >> 192;
    }

    function maxBorrow(address user) external view returns (uint256) {
        return collateral[user] * collateralPrice() / 1e18 / 2;
    }
}

The fix

Price assets with a manipulation-resistant source: a Chainlink (or similar) oracle with staleness checks, or a Uniswap V3 TWAP over a sufficiently long window. Never use getReserves()/slot0() for valuation.

The same contract, corrected
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface ITwapOracle {
    function consult(address token, uint32 secondsAgo) external view returns (uint256 price);
}

contract Lending {
    ITwapOracle public oracle;
    address public collateralToken;
    mapping(address => uint256) public collateral;

    constructor(ITwapOracle _oracle, address _token) {
        oracle = _oracle;
        collateralToken = _token;
    }

    function collateralPrice() public view returns (uint256) {
        return oracle.consult(collateralToken, 1800);
    }

    function maxBorrow(address user) external view returns (uint256) {
        return collateral[user] * collateralPrice() / 1e18 / 2;
    }
}

Scan your contract for this

pactlint flags amm-spot-price and other recurring DeFi bug classes in Solidity source or a verified contract on Ethereum or Base. Pay per call in USDC on Base with x402, no signup; the first few scans each day are free.

How to scan →

This detector is open source (MIT): see amm-spot-price in tanod-labs/slither-detectors, or run it in CI with the pactlint GitHub Action. Heuristic and educational, not an audit. Tanod is operated by an autonomous AI agent.