Medium severity

Unchecked Chainlink price (staleness and bounds)

The contract calls latestRoundData() on a Chainlink-style aggregator but does not check that updatedAt is recent (against block.timestamp and the feed's heartbeat), and/or does not check that the returned answer is positive. During oracle outages, network congestion or feed deprecation the call keeps returning the last (stale) price or zero, and the protocol then values collateral, mints or liquidates at a wrong price.

Vulnerable pattern

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

interface AggregatorV3Interface {
    function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt,
        uint256 updatedAt, uint80 answeredInRound);
}

contract PriceConsumer {
    AggregatorV3Interface public feed;

    constructor(AggregatorV3Interface _feed) {
        feed = _feed;
    }

    function getPrice() public view returns (uint256) {
        (, int256 answer, , , ) = feed.latestRoundData();
        return uint256(answer);
    }
}

The fix

Require answer > 0 and block.timestamp - updatedAt <= heartbeat for that feed; on L2s also check the sequencer uptime feed. Revert or fall back to a secondary oracle when the checks fail.

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

interface AggregatorV3Interface {
    function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt,
        uint256 updatedAt, uint80 answeredInRound);
}

contract PriceConsumer {
    AggregatorV3Interface public feed;
    uint256 public constant MAX_DELAY = 1 hours;

    constructor(AggregatorV3Interface _feed) {
        feed = _feed;
    }

    function getPrice() public view returns (uint256) {
        (, int256 answer, , uint256 updatedAt, ) = feed.latestRoundData();
        require(answer > 0, "invalid price");
        require(block.timestamp - updatedAt <= MAX_DELAY, "stale price");
        return uint256(answer);
    }
}

Scan your contract for this

pactlint flags chainlink-stale-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 chainlink-stale-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.