Chainlink预言机中的Data Feeds在智能合约中使用场景

332 阅读4分钟

前言

本文主要介绍使用chainlink预言机中Data Feeds,全文包含了MockV3Aggregator合约和PriceConsumer合约的开发、测试、部署。注意:为了便于测试,在本地区块节点上部署一个MockV3Aggregator。

Chainlink(去中心化预言机)

Chainlink定义:一个去中心化的预言机网络。它的主要作用是将区块链上的智能合约与现实世界的数据和事件连接起来;
Data Feeds(数据源):连接智能合约与现实世界数据的重要工具;
作用:Chainlink Data Feeds通过提供高质量、安全可靠的外部数据,极大地扩展了智能合约的应用范围和功能;

开发合约

本地部署MockV3Aggregator合约(便于本地测试使用)

说明注意

  1. chainlink的AggregatorV3Interface文件位置改变了;
  2. 本地部署变量声明和方法名要区分;
  3. 关于version方法把view修饰符改为pure
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";

contract MockV3Aggregator is AggregatorV3Interface {
    uint256 public constant versionvar = 4;

    uint8 public decimalsvar;
    int256 public latestAnswer;
    uint256 public latestTimestamp;
    uint256 public latestRound;
    mapping(uint256 => int256) public getAnswer;
    mapping(uint256 => uint256) public getTimestamp;
    mapping(uint256 => uint256) private getStartedAt;
    string private descriptionvar;

    constructor(
        uint8 _decimals,
        string memory _description,
        int256 _initialAnswer
    ) {
        decimalsvar = _decimals;
        descriptionvar = _description;
        updateAnswer(_initialAnswer);
    }

    function updateAnswer(int256 _answer) public {
        latestAnswer = _answer;
        latestTimestamp = block.timestamp;
        latestRound++;
        getAnswer[latestRound] = _answer;
        getTimestamp[latestRound] = block.timestamp;
        getStartedAt[latestRound] = block.timestamp;
    }

    function getRoundData(uint80 _roundId)
        external
        view
        override
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        )
    {
        return (
            _roundId,
            getAnswer[_roundId],
            getStartedAt[_roundId],
            getTimestamp[_roundId],
            _roundId
        );
    }

    function latestRoundData()
        external
        view
        override
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        )
    {
        return (
            uint80(latestRound),
            latestAnswer,
            getStartedAt[latestRound],
            latestTimestamp,
            uint80(latestRound)
        );
    }

    function decimals() external view override returns (uint8) {
        return decimalsvar;
    }

    function description() external view override returns (string memory) {
        return descriptionvar;
    }

    function version() external  pure override returns (uint256) {
        return versionvar;
    }
}
# 编译指令
# npx hardhat compile
PriceConsumer合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";

contract PriceConsumer {
    AggregatorV3Interface internal priceFeed;

    /**
     * 网络: Sepolia
     * 聚合器: ETH/USD
     * 地址: 0x694AA1769357215DE4FAC081bf1f309aDC325306
     */
    constructor(address _priceFeed) {
        priceFeed = AggregatorV3Interface(_priceFeed);
    }

    /**
     * 返回最新价格
     */
    function getLatestPrice() public view returns (int) {
        (
            /* uint80 roundID */,
            int price,
            /*uint startedAt*/,
            /*uint timeStamp*/,
            /*uint80 answeredInRound*/
        ) = priceFeed.latestRoundData();
        return price;
    }

    /**
     * 返回价格小数位数
     */
    function getDecimals() public view returns (uint8) {
        return priceFeed.decimals();
    }

    /**
     * 返回价格源描述
     */ 
    function getDescription() public view returns (string memory) {
        return priceFeed.description();
    }
}
# 编译指令
# npx hardhat compile

测试合约

const { ethers } = require("hardhat");
const { expect } = require("chai");
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");

describe("PriceConsumer", function () {
    // ETH/USD 价格配置
    const DECIMALS = 8;
    const INITIAL_PRICE = 200000000000; // $2000.00000000
    const UPDATED_PRICE = 210000000000; // $2100.00000000
    const DESCRIPTION = "ETH/USD Price Feed";

    async function deployFixture() {
        const [owner, addr1] = await ethers.getSigners();

        // 部署模拟价格源
        const MockV3Aggregator = await ethers.getContractFactory("MockV3Aggregator");
        const mockAggregator = await MockV3Aggregator.deploy(
            DECIMALS,
            DESCRIPTION,
            INITIAL_PRICE
        );

        // 部署价格消费者合约
        const PriceConsumer = await ethers.getContractFactory("PriceConsumer");
        const priceConsumer = await PriceConsumer.deploy(
            await mockAggregator.getAddress()
        );

        return { mockAggregator, priceConsumer, owner, addr1 };
    }

    describe("初始化", function () {
        it("价格信息源地址", async function () {
            const { mockAggregator, priceConsumer } = await loadFixture(deployFixture);
            expect(await priceConsumer.priceFeed()).to.equal(await mockAggregator.getAddress());
        });

        it("小数点位数", async function () {
            const { priceConsumer } = await loadFixture(deployFixture);
            expect(await priceConsumer.getDecimals()).to.equal(DECIMALS);
        });

        it("返回正确的描述", async function () {
            const { priceConsumer } = await loadFixture(deployFixture);
            expect(await priceConsumer.getDescription()).to.equal(DESCRIPTION);
        });
    });

    describe("价格更新", function () {
        it("初始价格", async function () {
            const { priceConsumer } = await loadFixture(deployFixture);
            expect(await priceConsumer.getLatestPrice()).to.equal(INITIAL_PRICE);
        });

        it("更新后的价格", async function () {
            const { mockAggregator, priceConsumer } = await loadFixture(deployFixture);
            
            await mockAggregator.updateAnswer(UPDATED_PRICE);
            expect(await priceConsumer.getLatestPrice()).to.equal(UPDATED_PRICE);
        });

        it("处理多个价格更新", async function () {
            const { mockAggregator, priceConsumer } = await loadFixture(deployFixture);
            
            const prices = [                210000000000, // $2100                220000000000, // $2200                215000000000  // $2150            ];

            for (const price of prices) {
                await mockAggregator.updateAnswer(price);
                expect(await priceConsumer.getLatestPrice()).to.equal(price);
            }
        });
    });

    describe("价格信息交互", function () {
        it("负价格的情况", async function () {
            const { mockAggregator, priceConsumer } = await loadFixture(deployFixture);
            
            const negativePrice = -100000000; // -$1
            await mockAggregator.updateAnswer(negativePrice);
            expect(await priceConsumer.getLatestPrice()).to.equal(negativePrice);
        });

        it("0价格的情况", async function () {
            const { mockAggregator, priceConsumer } = await loadFixture(deployFixture);
            
            await mockAggregator.updateAnswer(0);
            expect(await priceConsumer.getLatestPrice()).to.equal(0);
        });
    });

    describe("Gas费", function () {
        it("使用合理的气量(燃气量)进行价格查询", async function () {
            const { priceConsumer } = await loadFixture(deployFixture);
            
            const price = await priceConsumer.getLatestPrice();
            expect(price).to.equal(INITIAL_PRICE);
            
            // 获取交易的gas使用量
            const gasEstimate = await priceConsumer.getLatestPrice.estimateGas();
            expect(gasEstimate).to.be.lt(50000);
        });
    });
});
# 测试指令
# npx hardhat test ./test/xxx.js

部署合约

MockV3Aggregator合约
module.exports = async ({ getNamedAccounts, deployments }) => {
    const firstAccount=(await getNamedAccounts()).firstAccount;
  const { deploy, log } = deployments
  const MockV3Aggregator = await deploy("MockV3Aggregator", {
    contract: "MockV3Aggregator",
    from: firstAccount,
    log: true,
    args: [8,"ETH/USD", 200000000000], // decimals, description, initial answer (2000 USD with 8 decimals) 
  })
  console.log("MockV3Aggregator合约地址",MockV3Aggregator.address)
    
}
module.exports.tags = ["all", "MockV3Aggregator"]
# 部署指令
# npx hardhat deploy
PriceConsumer合约
module.exports = async ({ getNamedAccounts, deployments }) => {
    const firstAccount=(await getNamedAccounts()).firstAccount;
    const MockV3Aggregator=await deployments.get("MockV3Aggregator");
    const MockV3AggregatorAddress = MockV3Aggregator.address;
    const { deploy, log } = deployments
  const PriceConsumer = await deploy("PriceConsumer", {
    contract: "PriceConsumer",
    from: firstAccount,
    log: true,
    args: [MockV3AggregatorAddress],//合约地址
  })
  console.log("PriceConsumer合约地址",PriceConsumer.address)
}
module.exports.tags = ["all", "PriceConsumer"]
# 部署指令
# npx hardhat deploy

总结

以上就是预言机中的数据流的智能合约的开发、测试、部署以及概念和作用的介绍;喂价主要场景的使用集中在代币之间的转换、结算;