预言机集成

核心知识点讲解

预言机的作用

预言机是连接区块链与外部世界的桥梁,解决了智能合约无法直接访问链外数据的问题:

  • 数据获取:从外部API获取价格、天气、体育赛事结果等数据
  • 事件触发:基于链外事件触发智能合约操作
  • 跨链通信:在不同区块链之间传递信息
  • 随机数生成:提供可验证的随机数

预言机的工作原理

  1. 请求发起:智能合约向预言机发送数据请求
  2. 数据获取:预言机节点从外部数据源获取数据
  3. 数据验证:预言机网络验证数据的准确性
  4. 数据返回:预言机将验证后的数据发送回智能合约
  5. 执行操作:智能合约使用返回的数据执行相应操作

Chainlink预言机

Chainlink是目前最流行的去中心化预言机网络:

  • 去中心化:由多个节点组成的网络,确保数据的可靠性
  • 安全:使用密码学技术确保数据传输的安全性
  • 灵活:支持多种数据源和自定义请求
  • 广泛应用:被众多DeFi项目和NFT项目采用

实用案例分析

价格预言机实现

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

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

contract PriceConsumerV3 {
    // 不同资产的价格预言机地址
    mapping(string => address) public priceFeedAddresses;
    
    constructor() {
        // 初始化主要资产的价格预言机地址(测试网络)
        priceFeedAddresses["ETH/USD"] = 0x694AA1769357215DE4FAC081bf1f309aDC325306;
        priceFeedAddresses["BTC/USD"] = 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43;
        priceFeedAddresses["LINK/USD"] = 0xc59E3633BAAC79493d908e63626716e204A45EdF;
    }
    
    // 获取最新价格
    function getLatestPrice(string memory pair) public view returns (int) {
        address priceFeedAddress = priceFeedAddresses[pair];
        require(priceFeedAddress != address(0), "Price feed not found");
        
        AggregatorV3Interface priceFeed = AggregatorV3Interface(priceFeedAddress);
        (uint80 roundID, int price, uint startedAt, uint timeStamp, uint80 answeredInRound) = priceFeed.latestRoundData();
        return price;
    }
    
    // 获取价格精度
    function getDecimals(string memory pair) public view returns (uint8) {
        address priceFeedAddress = priceFeedAddresses[pair];
        require(priceFeedAddress != address(0), "Price feed not found");
        
        AggregatorV3Interface priceFeed = AggregatorV3Interface(priceFeedAddress);
        return priceFeed.decimals();
    }
    
    // 添加新的价格预言机
    function addPriceFeed(string memory pair, address feedAddress) public {
        priceFeedAddresses[pair] = feedAddress;
    }
}

自定义预言机请求

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

import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol";

contract APIConsumer is ChainlinkClient, ConfirmedOwner {
    using Chainlink for Chainlink.Request;
    
    uint256 public volume;
    bytes32 private jobId;
    uint256 private fee;
    
    event RequestVolume(bytes32 indexed requestId, uint256 volume);
    
    constructor() ConfirmedOwner(msg.sender) {
        setChainlinkToken(0x326C977E6efc84E512bB9C30f76E30c160eD06FB); // 测试网络LINK代币地址
        setChainlinkOracle(0xCC79157eb46F5624204f47AB42b3906cAA40eaB7); // 测试网络预言机地址
        jobId = "7d80a6386ef543a3abb52817f6707e3b"; // 测试网络HTTP GET作业ID
        fee = (1 * LINK_DIVISIBILITY) / 10; // 0.1 LINK
    }
    
    // 发起API请求
    function requestVolumeData() public returns (bytes32 requestId) {
        Chainlink.Request memory req = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);
        
        // 设置API URL
        req.add("get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD");
        
        // 设置路径以解析JSON响应
        req.add("path", "RAW.ETH.USD.VOLUME24HOUR");
        
        // 发送请求
        return sendChainlinkRequest(req, fee);
    }
    
    // 处理预言机响应
    function fulfill(bytes32 _requestId, uint256 _volume) public recordChainlinkFulfillment(_requestId) {
        volume = _volume;
        emit RequestVolume(_requestId, _volume);
    }
    
    // 紧急撤回LINK
    function withdrawLink() public onlyOwner {
        LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress());
        require(link.transfer(msg.sender, link.balanceOf(address(this))), "Unable to transfer");
    }
}

预言机在DeFi中的应用

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

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

contract DeFiLending is Ownable {
    // 价格预言机
    AggregatorV3Interface internal ethPriceFeed;
    
    // 抵押率
    uint256 public collateralRatio = 150; // 150%
    
    // 用户存款
    mapping(address => uint256) public ethDeposited;
    mapping(address => uint256) public usdBorrowed;
    
    event Deposit(address indexed user, uint256 amount);
    event Borrow(address indexed user, uint256 amount);
    event Repay(address indexed user, uint256 amount);
    event Liquidate(address indexed user, uint256 amount);
    
    constructor() {
        // 初始化ETH/USD价格预言机
        ethPriceFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306);
    }
    
    // 获取ETH最新价格(单位:USD)
    function getEthPrice() public view returns (uint256) {
        (uint80 roundID, int price, uint startedAt, uint timeStamp, uint80 answeredInRound) = ethPriceFeed.latestRoundData();
        uint8 decimals = ethPriceFeed.decimals();
        return uint256(price) * 10 ** (18 - decimals);
    }
    
    // 计算抵押品价值
    function getCollateralValue(address user) public view returns (uint256) {
        uint256 ethAmount = ethDeposited[user];
        uint256 ethPrice = getEthPrice();
        return ethAmount * ethPrice / 10 ** 18;
    }
    
    // 计算最大可借金额
    function getMaxBorrow(address user) public view returns (uint256) {
        uint256 collateralValue = getCollateralValue(user);
        return collateralValue * 100 / collateralRatio;
    }
    
    // 检查是否需要清算
    function checkLiquidation(address user) public view returns (bool) {
        uint256 collateralValue = getCollateralValue(user);
        uint256 borrowed = usdBorrowed[user];
        return borrowed > 0 && (collateralValue * 100 / borrowed) < collateralRatio;
    }
    
    // 存款ETH
    function deposit() external payable {
        ethDeposited[msg.sender] += msg.value;
        emit Deposit(msg.sender, msg.value);
    }
    
    // 借款USD
    function borrow(uint256 amount) external {
        require(amount <= getMaxBorrow(msg.sender), "Insufficient collateral");
        usdBorrowed[msg.sender] += amount;
        emit Borrow(msg.sender, amount);
    }
    
    // 还款
    function repay(uint256 amount) external {
        require(amount <= usdBorrowed[msg.sender], "Repay amount exceeds borrowed");
        usdBorrowed[msg.sender] -= amount;
        emit Repay(msg.sender, amount);
    }
    
    // 清算
    function liquidate(address user) external {
        require(checkLiquidation(user), "User not eligible for liquidation");
        uint256 borrowed = usdBorrowed[user];
        uint256 collateral = ethDeposited[user];
        
        usdBorrowed[user] = 0;
        ethDeposited[user] = 0;
        
        payable(msg.sender).transfer(collateral);
        emit Liquidate(user, borrowed);
    }
    
    // 提取ETH
    function withdraw(uint256 amount) external {
        require(ethDeposited[msg.sender] >= amount, "Insufficient deposit");
        require(!checkLiquidation(msg.sender), "Collateral ratio too low");
        
        ethDeposited[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
    }
}

多数据源预言机

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

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

contract MultiSourceOracle {
    // 多个价格预言机地址
    address[] public priceFeeds;
    
    constructor(address[] memory _priceFeeds) {
        priceFeeds = _priceFeeds;
    }
    
    // 获取平均价格
    function getAveragePrice() public view returns (int) {
        require(priceFeeds.length > 0, "No price feeds configured");
        
        int sum = 0;
        for (uint i = 0; i < priceFeeds.length; i++) {
            AggregatorV3Interface priceFeed = AggregatorV3Interface(priceFeeds[i]);
            (uint80 roundID, int price, uint startedAt, uint timeStamp, uint80 answeredInRound) = priceFeed.latestRoundData();
            sum += price;
        }
        
        return sum / int(priceFeeds.length);
    }
    
    // 获取中位数价格
    function getMedianPrice() public view returns (int) {
        require(priceFeeds.length > 0, "No price feeds configured");
        
        int[] memory prices = new int[](priceFeeds.length);
        for (uint i = 0; i < priceFeeds.length; i++) {
            AggregatorV3Interface priceFeed = AggregatorV3Interface(priceFeeds[i]);
            (uint80 roundID, int price, uint startedAt, uint timeStamp, uint80 answeredInRound) = priceFeed.latestRoundData();
            prices[i] = price;
        }
        
        // 排序
        for (uint i = 0; i < prices.length - 1; i++) {
            for (uint j = i + 1; j < prices.length; j++) {
                if (prices[i] > prices[j]) {
                    int temp = prices[i];
                    prices[i] = prices[j];
                    prices[j] = temp;
                }
            }
        }
        
        // 返回中位数
        if (prices.length % 2 == 0) {
            return (prices[prices.length / 2 - 1] + prices[prices.length / 2]) / 2;
        } else {
            return prices[prices.length / 2];
        }
    }
    
    // 添加价格预言机
    function addPriceFeed(address feedAddress) public {
        priceFeeds.push(feedAddress);
    }
    
    // 移除价格预言机
    function removePriceFeed(uint index) public {
        require(index < priceFeeds.length, "Index out of bounds");
        
        for (uint i = index; i < priceFeeds.length - 1; i++) {
            priceFeeds[i] = priceFeeds[i + 1];
        }
        priceFeeds.pop();
    }
}

实践练习

  1. 部署价格预言机合约

    • 部署 PriceConsumerV3 合约
    • 调用 getLatestPrice 方法获取ETH/USD价格
    • 测试不同资产的价格获取
  2. 创建自定义API请求

    • 部署 APIConsumer 合约
    • 为合约充值LINK代币
    • 调用 requestVolumeData 方法
    • 等待预言机响应并检查 volume 变量
  3. 实现DeFi借贷平台

    • 部署 DeFiLending 合约
    • 存款ETH
    • 基于抵押品借款
    • 测试清算功能
  4. 使用多数据源预言机

    • 部署 MultiSourceOracle 合约,添加多个价格预言机
    • 调用 getAveragePricegetMedianPrice 方法
    • 比较不同方法的结果

总结

预言机是Web3开发中的关键组件,它使智能合约能够与外部世界交互,获取实时数据和触发事件。Chainlink作为领先的去中心化预言机网络,提供了安全、可靠的数据获取解决方案。

在集成预言机时,需要注意以下几点:

  • 安全性:选择可靠的预言机服务,避免单一数据源
  • 成本:预言机服务需要支付一定的费用(如LINK代币)
  • 延迟:预言机请求和响应需要一定的时间
  • 准确性:确保获取的数据准确反映真实世界的情况

通过本集的学习,你应该能够理解预言机的工作原理和集成方法,为开发需要外部数据的Web3应用做好准备。预言机的应用场景非常广泛,包括DeFi价格 feeds、NFT随机属性生成、体育博彩结果验证等,是构建复杂Web3应用的重要工具。

« 上一篇 时间锁和权限管理 下一篇 » 多签钱包实现