2. 实现价格预言机接口

任务目标

为去中心化永续合约系统接入Chainlink价格预言机,实现标的资产(ETH/USD)实时价格的获取与验证。本案例将完成一个可直接部署的Solidity合约,包含价格获取、历史价格记录、以及基于偏差阈值的安全校验逻辑。

环境依赖

  • Solidity ^0.8.19
  • Chainlink AggregatorV3Interface(npm包:@chainlink/contracts)
  • 开发框架:Hardhat或Foundry(任选)
  • 测试网络:Sepolia(Chainlink ETH/USD Feed地址:0x694AA1769357215DE4FAC081bf1f309aDC325306)

核心实现

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

/**
 * @title PriceOracle
 * @dev 获取并验证Chainlink价格数据,供永续合约调用
 */
contract PriceOracle {
    AggregatorV3Interface internal immutable priceFeed;
    
    // 价格偏差容忍度(以1e18为基数,0.5% = 5e15)
    uint256 public constant DEVIATION_TOLERANCE = 5e15;
    
    // 价格新鲜度阈值(秒)
    uint256 public constant STALE_PRICE_TIMEOUT = 2 hours;
    
    // 记录最近一次价格更新时间
    uint256 public lastUpdatedAt;
    
    // 历史价格记录(时间戳 => 价格)
    mapping(uint256 => uint256) public priceHistory;
    
    // 价格更新事件
    event PriceUpdated(uint256 indexed timestamp, uint256 price);
    event PriceValidationFailed(uint256 indexed timestamp, uint256 price, string reason);

    /**
     * @param _priceFeed Chainlink ETH/USD Feed地址
     */
    constructor(address _priceFeed) {
        require(_priceFeed != address(0), "Invalid feed address");
        priceFeed = AggregatorV3Interface(_priceFeed);
    }

    /**
     * @dev 获取最新价格(带验证)
     * @return price 最新价格(8位小数,需转换为18位)
     * @return timestamp 价格时间戳
     */
    function getLatestPrice() external returns (uint256 price, uint256 timestamp) {
        (
            uint80 roundID,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        ) = priceFeed.latestRoundData();
        
        // 验证1:价格非负
        require(answer > 0, "Invalid price");
        
        // 验证2:价格新鲜度
        require(block.timestamp - updatedAt <= STALE_PRICE_TIMEOUT, "Stale price");
        
        // 验证3:轮次完整性
        require(answeredInRound >= roundID, "Incomplete round");
        
        // 转换为18位精度
        price = uint256(answer) * 1e10;
        timestamp = updatedAt;
        
        // 记录历史价格
        priceHistory[timestamp] = price;
        lastUpdatedAt = timestamp;
        
        emit PriceUpdated(timestamp, price);
        return (price, timestamp);
    }

    /**
     * @dev 获取历史价格
     * @param _timestamp 查询的时间戳
     * @return 该时间点的价格(18位精度)
     */
    function getHistoricalPrice(uint256 _timestamp) external view returns (uint256) {
        uint256 price = priceHistory[_timestamp];
        require(price > 0, "Price not found");
        return price;
    }

    /**
     * @dev 验证价格偏差是否在容忍范围内
     * @param _currentPrice 当前获取的价格
     * @param _referencePrice 参考价格(如上一笔成交价)
     * @return 是否在容忍范围内
     */
    function validatePriceDeviation(uint256 _currentPrice, uint256 _referencePrice) 
        external 
        pure 
        returns (bool) 
    {
        if (_referencePrice == 0) return true;
        
        uint256 diff = _currentPrice > _referencePrice ? 
            _currentPrice - _referencePrice : 
            _referencePrice - _currentPrice;
            
        uint256 deviation = (diff * 1e18) / _referencePrice;
        
        if (deviation > DEVIATION_TOLERANCE) {
            emit PriceValidationFailed(block.timestamp, _currentPrice, "Deviation too high");
            return false;
        }
        return true;
    }

    /**
     * @dev 获取价格Feed的decimals
     */
    function getDecimals() external view returns (uint8) {
        return priceFeed.decimals();
    }
}

部署与调用

// 部署脚本(Hardhat)
const { ethers } = require("hardhat");

async function main() {
    const PriceOracle = await ethers.getContractFactory("PriceOracle");
    const oracle = await PriceOracle.deploy(
        "0x694AA1769357215DE4FAC081bf1f309aDC325306" // Sepolia ETH/USD Feed
    );
    await oracle.deployed();
    console.log("PriceOracle deployed to:", oracle.address);
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});

验证方法

  1. 部署验证:在Sepolia测试网部署合约,确认构造函数接收的Feed地址正确。
  2. 价格获取测试:调用getLatestPrice(),预期返回约18位精度的ETH/USD价格(例如当前价格约2000 USDT,则返回约2000 * 1e18)。
  3. 新鲜度验证:检查返回的timestamp与当前区块时间差是否小于2小时。
  4. 偏差验证:调用validatePriceDeviation(2000e18, 1990e18),预期返回true(偏差0.5%以内);调用validatePriceDeviation(2000e18, 1900e18),预期返回false(偏差5%)。
  5. 历史记录验证:获取最新价格后,用返回的时间戳调用getHistoricalPrice(timestamp),应返回相同价格。

关键说明

  • Chainlink返回的价格为8位小数,合约内统一转换为18位精度,便于后续永续合约计算。
  • 价格新鲜度检查防止使用长时间未更新的陈旧数据。
  • 轮次完整性检查确保数据来自完整更新的轮次,而非中间状态。
  • 偏差验证函数供永续合约在交易前校验价格合理性,防止极端价格冲击。