预言机
预言机
本页内容正在整理中,欢迎贡献。
概述
智能合约无法直接访问链外数据(如资产价格、天气、随机数)。预言机(Oracle)是连接区块链与现实世界的数据桥梁。选择可靠的预言机对于 DeFi 协议的安全性至关重要——许多重大安全事故正是源于预言机被操纵。
主要内容
预言机的分类
| 类型 | 说明 | 代表 |
|---|---|---|
| 价格 Feed | 提供资产实时价格 | Chainlink Price Feeds |
| 随机数 | 提供可验证随机数 | Chainlink VRF |
| 自动化 | 定时/条件触发合约 | Chainlink Automation |
| 跨链消息 | 跨链数据传递 | Chainlink CCIP |
| 低延迟价格 | 高频更新(DEX 场景) | Pyth Network |
| 去中心化 API | 任意 API 数据上链 | API3 |
Chainlink Price Feeds
最广泛使用的去中心化价格预言机:
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract PriceConsumer { AggregatorV3Interface internal priceFeed;
// ETH/USD 主网地址: 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 constructor(address _feedAddress) { priceFeed = AggregatorV3Interface(_feedAddress); }
function getLatestPrice() public view returns (int256, uint256) { ( /* uint80 roundId */, int256 price, /* uint256 startedAt */, uint256 updatedAt, /* uint80 answeredInRound */ ) = priceFeed.latestRoundData();
// 安全检查:确保数据不过时 require(block.timestamp - updatedAt <= 3600, "Price data is stale"); require(price > 0, "Invalid price");
return (price, updatedAt); }
function getDecimals() public view returns (uint8) { return priceFeed.decimals(); // ETH/USD 返回 8 }}常用价格 Feed 地址(以太坊主网):
| 对 | 地址 |
|---|---|
| ETH/USD | 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 |
| BTC/USD | 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c |
| USDC/USD | 0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6 |
| LINK/USD | 0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c |
查询所有可用 Feed:data.chain.link
Chainlink VRF(可验证随机数)
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2Plus.sol";import "@chainlink/contracts/src/v0.8/vrf/interfaces/IVRFCoordinatorV2Plus.sol";
contract RandomNFT is VRFConsumerBaseV2Plus { // 主网配置 address constant COORDINATOR = 0xD7f86b4b8Cae7D942340FF628F82735b7a20893a; bytes32 constant KEY_HASH = 0x8...; // 参考 Chainlink 文档 uint256 immutable subscriptionId;
mapping(uint256 => address) public requestIdToMinter;
constructor(uint256 _subId) VRFConsumerBaseV2Plus(COORDINATOR) { subscriptionId = _subId; }
function requestRandomMint() external returns (uint256 requestId) { requestId = s_vrfCoordinator.requestRandomWords( VRFV2PlusClient.RandomWordsRequest({ keyHash: KEY_HASH, subId: subscriptionId, requestConfirmations: 3, // 等待 3 个区块确认 callbackGasLimit: 100_000, numWords: 1, extraArgs: VRFV2PlusClient._argsToBytes( VRFV2PlusClient.ExtraArgsV1({ nativePayment: false }) ) }) ); requestIdToMinter[requestId] = msg.sender; }
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override { address minter = requestIdToMinter[requestId]; uint256 tokenId = randomWords[0] % 10000; // 0-9999 的随机 ID _mint(minter, tokenId); }}Pyth Network(低延迟价格)
适合 DEX、永续合约等需要高频价格更新的场景:
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
contract PythPriceExample { IPyth pyth;
bytes32 constant ETH_USD_PRICE_ID = 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace;
constructor(address _pyth) { pyth = IPyth(_pyth); }
function getETHPrice(bytes[] calldata priceUpdateData) external payable { // 支付更新费用并更新价格 uint fee = pyth.getUpdateFee(priceUpdateData); pyth.updatePriceFeeds{value: fee}(priceUpdateData);
// 获取价格(60 秒内有效) PythStructs.Price memory price = pyth.getPriceNoOlderThan( ETH_USD_PRICE_ID, 60 );
// price.price 为整数,price.expo 为指数(通常为 -8) // 实际价格 = price.price * 10^price.expo }}预言机安全注意事项
- 检查数据新鲜度:始终验证
updatedAt时间戳,拒绝过期数据 - 检查正数:确保价格 > 0
- 使用 TWAP:用时间加权平均价格替代即时价格,防止价格操纵
- 多源验证:关键应用使用多个独立预言机交叉验证
- 避免使用 DEX 即时价格:Uniswap Spot Price 可以被 Flash Loan 操纵
// ❌ 危险:使用 DEX 即时价格uint price = IUniswapV3Pool(pool).slot0().sqrtPriceX96; // 可被操纵!
// ✅ 安全:使用 Chainlink 价格 Feed(int256 price,) = getLatestPrice();