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;
});
验证方法
- 部署验证:在Sepolia测试网部署合约,确认构造函数接收的Feed地址正确。
- 价格获取测试:调用
getLatestPrice(),预期返回约18位精度的ETH/USD价格(例如当前价格约2000 USDT,则返回约2000 * 1e18)。 - 新鲜度验证:检查返回的
timestamp与当前区块时间差是否小于2小时。 - 偏差验证:调用
validatePriceDeviation(2000e18, 1990e18),预期返回true(偏差0.5%以内);调用validatePriceDeviation(2000e18, 1900e18),预期返回false(偏差5%)。 - 历史记录验证:获取最新价格后,用返回的时间戳调用
getHistoricalPrice(timestamp),应返回相同价格。
关键说明
- Chainlink返回的价格为8位小数,合约内统一转换为18位精度,便于后续永续合约计算。
- 价格新鲜度检查防止使用长时间未更新的陈旧数据。
- 轮次完整性检查确保数据来自完整更新的轮次,而非中间状态。
- 偏差验证函数供永续合约在交易前校验价格合理性,防止极端价格冲击。