预言机集成
核心知识点讲解
预言机的作用
预言机是连接区块链与外部世界的桥梁,解决了智能合约无法直接访问链外数据的问题:
- 数据获取:从外部API获取价格、天气、体育赛事结果等数据
- 事件触发:基于链外事件触发智能合约操作
- 跨链通信:在不同区块链之间传递信息
- 随机数生成:提供可验证的随机数
预言机的工作原理
- 请求发起:智能合约向预言机发送数据请求
- 数据获取:预言机节点从外部数据源获取数据
- 数据验证:预言机网络验证数据的准确性
- 数据返回:预言机将验证后的数据发送回智能合约
- 执行操作:智能合约使用返回的数据执行相应操作
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();
}
}实践练习
部署价格预言机合约:
- 部署
PriceConsumerV3合约 - 调用
getLatestPrice方法获取ETH/USD价格 - 测试不同资产的价格获取
- 部署
创建自定义API请求:
- 部署
APIConsumer合约 - 为合约充值LINK代币
- 调用
requestVolumeData方法 - 等待预言机响应并检查
volume变量
- 部署
实现DeFi借贷平台:
- 部署
DeFiLending合约 - 存款ETH
- 基于抵押品借款
- 测试清算功能
- 部署
使用多数据源预言机:
- 部署
MultiSourceOracle合约,添加多个价格预言机 - 调用
getAveragePrice和getMedianPrice方法 - 比较不同方法的结果
- 部署
总结
预言机是Web3开发中的关键组件,它使智能合约能够与外部世界交互,获取实时数据和触发事件。Chainlink作为领先的去中心化预言机网络,提供了安全、可靠的数据获取解决方案。
在集成预言机时,需要注意以下几点:
- 安全性:选择可靠的预言机服务,避免单一数据源
- 成本:预言机服务需要支付一定的费用(如LINK代币)
- 延迟:预言机请求和响应需要一定的时间
- 准确性:确保获取的数据准确反映真实世界的情况
通过本集的学习,你应该能够理解预言机的工作原理和集成方法,为开发需要外部数据的Web3应用做好准备。预言机的应用场景非常广泛,包括DeFi价格 feeds、NFT随机属性生成、体育博彩结果验证等,是构建复杂Web3应用的重要工具。