ChainLink预言机和Hardhat框架使用

一、关于预言机
1、为什么要使用预言机?

        因为区块链的智能合约,是在沙箱中运行的,是没有办法获取区块链以外的数据的,外部API提供的数据和任何其他链下资源都是无法被区块链获取的;

        如果在区块链中运行的程序(智能合约)能够从外部直接获取数据,调用API接口,那么智能合约将变得毫无意义,里面到处都是从外部API获取的可变数据,那么区块链的不可篡改性也将丢失。所以,沙箱机制是很有必要的!

1668133386956046.png

        但是有些场景下,我们智能合约程序运行时,不得不从外部(如互联网)获取最新数据,如DEFI板块对货币价格的依赖,农业系统对温湿度的依赖,支付系统对外部支付状态的依赖,等等;这时候该怎么办?预言机就出现了,预言机也就成了区块链基础设施!

2、预言机的分类

  • 中心化预言机:

        在中心化的预言机服务中,预言机会有被攻击的可能性,这导致智能合约丢失了确定性和可靠性这一最关键的特征,从而使大多数基于现实场景的智能合约用例的不可用。Oraclize市场上中心化的预言机,是一个为以太坊提供中心化数据传输预言机服务的项目,其依托亚马逊AWS服务和TLSNotary证明技术,提供预言机的服务。它是中心化的,而且TLSNotary要花费很多的Gas,这笔消耗最终还有要由用户买单。

  • 去中心化预言机:

        为了解决中心化预言机存在被攻击的可能性,从而导致智能合约丢失确定性和可靠性,去中心化的预言机网络就诞生了。Chainlink是以太坊区块链上第一个被提出的去中心化预言机解决方案。比起Oraclize的中心化,Chainlink更符合区块链去中心化的准则。Chainlink主要提供用于帮助智能合约访问关键链外资源、网站API和传统银行账户支付的预言机服务。


二、编写智能合约,从Chainlink预言机获取最新交易对的市场价

Chainlink官网:https://chain.link/

Chainlink github:https://github.com/smartcontractkit/chainlink

Chainlink在线文档:https://docs.chain.link/ethereum/

1、以Goerli网的 BTC/ETH 交易对为例:

1668151513805423.png

记住这2个交易对Address!

2、需要用到的类库、接口等可以从官方github拷贝或引用:

https://github.com/smartcontractkit/chainlink/tree/develop/contracts/src/v0.8/interfaces

3、编写 PriceFeed.sol 合约代码:

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

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

contract PriceFeed {

    // BTC/ETH
    AggregatorV3Interface internal priceFeedBE;  
    // BTC/USD
    AggregatorV3Interface internal priceFeedBU;

    constructor(){
        priceFeedBE = AggregatorV3Interface(0x779877A7B0D9E8603169DdbD7836e478b4624789);
        priceFeedBU = AggregatorV3Interface(0xA39434A63A52E749F02807ae27335515BA4b07F7);
    }

/**priceFeed.latestRoundData()返回值为5个数据:
    uint80 internal roundId:第几轮,因为价格的更新是一轮一轮地(数据是轮询Feed给预言机的)
    int256 internal answer:真正的价格数据,就是我们需要的内容
    uint256 internal startedAt:这一轮价格的开始时间
    uint256 internal updatedAt:这一轮价格的开始结束时间
    uint80 internal answeredInRound:我们得到的价格的roundId,这里肯定是和第一个roundId相等的;
**/
    function getBEPrice() public view returns (int256){
        (
            , 
            int256 answer, 
            , 
            , 
        ) = priceFeedBE.latestRoundData();
        return answer;
    }

    function getBUPrice() public view returns (int256){
        (
            , 
            int256 answer, 
            , 
            , 
        ) = priceFeedBU.latestRoundData();
        return answer;
    }
}

4、部署合约,并进行测试:

1668151712480527.png

需要注意的是,在Chainlink的返回结果中:

  • 如果是ETH交易对:小数点为18位;

  • 如果是USD交易对:小数点为8位;

经过与现实中的交易对价格相比,数据正确!


三、Hardhat框架的简单说明

1、既然有Remix,为什么还要用开发框架?

因为Remix在线IDE开发一些小项目,单页面合约还行,但是如果是大型项目,团队迭代开发,多人协作,集成测试,单元测试等,那么Remix显然是不能满足的!

2、Hardhat 与 Truffle 框架的对比:

  • Hardhat:

        Hardhat是最好的框架之一,拥有最快的测试、最好的教程和最简单的集成方式。老实说,每个喜欢JS框架的人都应该在尝试一下Hardhat。它真的很容易上手,可以快速进行测试。Hardhat的Discord对问题的回复也一直很迅速,所以如果你遇到问题,你可以随时寻求帮助。Hardhat使用Waffle和Ethers.js进行测试,这是更好的JavaScript智能合约框架,由于比web3.js有一些非常好的改进。他们还可以直接集成OpenZeppelin的可升级智能合约插件,这是一个巨大的进步。

        这个项目有一种很棒的感觉。它是干净的。它做你想要它做的事情。它的速度非常快。这个项目正在不断改进,他们显然致力于让智能合约开发者的生活更加轻松。

  • Truffle:

        长久以来Truffle都是开发框架的默认选项,这是有原因的。它是一个强大的框架,为许多其他框架树立了标准。你会发现使用Truffle的项目最多,所以他的应用例子很容易找到。Truffle还可以很容易地与它的姐妹工具Drizzle和Ganache集成。特别是Ganache是工程师运行本地区块链的最受欢迎的方式之一。对于那些寻求更多工具的人来说,你可以付费升级到Truffle团队账户,以获得智能合约持续集成、可视化部署和监控的功能。他们还可以直接集成OpenZeppelin的可升级智能合约插件,这是一个巨大的进步。显然,Truffle拥有一群才华横溢的工程师,他们希望智能合约能够更加广泛地应用在这个世界上。

        Truffle测试的运行速度没有Hardhat的快,而且由于用户量大,获得支持也很困难。我很期待看到他们被ConsenSys收购后如何改进这个项目。他们的文档似乎开始落后,可能很难根据,但如果你在谷歌上搜索一个你遇到的错误,你很可能会遇到一个曾经遇到错误并解决它的人。我发现一些改进项目的最好方法就是在他们的GitHub上留下问题。不管怎么说,让生态系统越来越强大,越来越好是我们开源的职责!

        由于几乎所有开发者都很熟悉Truffle,得到同行的支持通常很容易。我真的希望看到团队可以更多地支持这个项目,因为他们有这么多用户。我希望他们看到这篇文章,并努力改进他们的文档,以便他们能够保持作为测试和部署智能合约的首选平台之一。


四、使用Hardhat框架对上面的DataFeed功能进行编写、部署和单元测试

1、创建项目文件夹:

PS E:\Study-Code\blockchain> mkdir DataFeedDemo
PS E:\Study-Code\blockchain> cd .\DataFeedDemo\

2、初始化一个npm程序:

# 初始化一个npm程序,便于管理依赖包
PS E:\Study-Code\blockchain\DataFeedDemo> npm init -y
Wrote to E:\Study-Code\blockchain\DataFeedDemo\package.json:

{
  "name": "DataFeedDemo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

# 此时文件夹中多了个package.json,是用来记录安装哪些包的文件
PS E:\Study-Code\blockchain\DataFeedDemo> ls

    目录: E:\Study-Code\blockchain\DataFeedDemo
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2022-11-11     15:43            226 package.json

3、安装hardhat:

# 安装hardhat
PS E:\Study-Code\blockchain\DataFeedDemo> npm install --save-dev hardhat

# 安装完成后
PS E:\Study-Code\blockchain\DataFeedDemo> cat .\package.json
{
  "name": "DataFeedDemo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "hardhat": "^2.12.2"
  }
}

4、通过 npx 初始化一个hardhat项目:

PS E:\Study-Code\blockchain\DataFeedDemo> npx hardhat init

1668158079247433.png

这里我们选择创建一个空的 hardhat.conf.js 文件,之后再看看多了哪些文件:

PS E:\Study-Code\blockchain\DataFeedDemo> ls


    目录: E:\Study-Code\blockchain\DataFeedDemo


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2022-11-11     17:10                node_modules
-a----        2022-11-11     17:16            100 hardhat.config.js
-a----        2022-11-11     17:10         119034 package-lock.json
-a----        2022-11-11     17:10            279 package.json

此时,我们就可以用vscode打开本项目进行编码了

5、创建文件夹和合约文件 ./contracts/PriceFeed.sol :

合约代码与在Remix中的代码一模一样!

6、通过 hardhat 对合约进行编译:

# 因为我们代码中用到了@chainlink/contracts,所以得先安装
PS E:\Study-Code\blockchain\DataFeedDemo> npm install --save-dev @chainlink/contracts

# 使用 hardhat 对合约进行编译
PS E:\Study-Code\blockchain\DataFeedDemo> npx hardhat compile
Downloading compiler 0.8.17
Compiled 2 Solidity files successfully

# 编译完成后,在 artifacts\contracts\PriceFeed.sol\ 目录下会生成对应的abi
PS E:\Study-Code\blockchain\DataFeedDemo> ls .\artifacts\contracts\PriceFeed.sol\
    目录: E:\Study-Code\blockchain\DataFeedDemo\artifacts\contracts\PriceFeed.sol

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2022-11-11     17:45            108 PriceFeed.dbg.json
-a----        2022-11-11     17:45           4584 PriceFeed.json

7、为我们的合约编写一个测试用例 ./test/PriceFeedTest.js

安装测试用例中需要用到的工具包:

PS E:\Study-Code\blockchain\DataFeedDemo> npm install --save-dev chai-bn

# 在项目中通过以下方式进行引用:
const chai =  require("chai");
const BN = require("bn.js");

PriceFeedTest.js代码如下:

const { ethers } = require("hardhat");  //ethers.js
const { expect } = require("chai");  //javascript的测试框架

const chai =  require("chai");
const BN = require("bn.js");
chai.use(require('chai-bn')(BN));

describe("PriceFeed Demo Test", function(){
    it("check if return price > 0", async function(){
        //部署合约
        const priceFeed = await ethers.getContractFactory("PriceFeed");
        const priceFeedContract = await priceFeed.deploy();
        await priceFeedContract.deployed();
        console.log("the contract is deployed successed!")

        // 因为返回值不是字符串,所以需要工具转换一下
        const resultBE = await priceFeedContract.getBEPrice();
        const resultBU = await priceFeedContract.getBUPrice();
        const resultStrBE = new ethers.BigNumber.from(resultBE).toString();
        const resultStrBU = new ethers.BigNumber.from(resultBU).toString();
        console.log("the resturn price is", resultStrBE);
        console.log("the resturn price is", resultStrBU);

        // 断言
        expect(resultStrBE).to.be.bignumber.greaterThan("0");
        expect(resultStrBU).to.be.bignumber.greaterThan("0");
    })
})

8、为了使用Goerli测试网,我们需要修改hardhat.config.js配置:

先全局安装 hardhat-toolbox 工具包:

https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-toolbox

npm install --save-dev @nomicfoundation/hardhat-toolbox

# 但是如果我们使用的是旧版本的npm,还需要安装其他hardhat依赖:
npm install --save-dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomiclabs/hardhat-ethers @nomiclabs/hardhat-etherscan chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v5 @ethersproject/abi @ethersproject/providers

hardhat.config.js 文件内容添加网络 networks:

// 因为hardhat框架全局都需要使用hardhat-toolbox,所以在这里引入
require("@nomicfoundation/hardhat-toolbox"); 

/** @type import('hardhat/config').HardhatUserConfig */
const account = "b627fa1dca25520.....................803e0a56451a4055e967";
const rpcUrl = "https://goerli.infura.io/v3/be0c2062b1294239a705813646553380";

module.exports = {
  solidity: "0.8.17",
  networks: {
    goerli: {
      accounts: [account], //钱包的私钥
      url: rpcUrl      //节点rpc地址
    }
  }
};

9、使用 hardhat 框架进行单元测试:

PS E:\Study-Code\blockchain\DataFeedDemo> npx hardhat test --network goerli


  PriceFeed Demo Test
the contract is deployed successed!
the resturn price is 13622122326658490000
the resturn price is 1731419300000
    ✔ check if return price > 0 (26918ms)


  1 passing (27s)

单元测试通过!

10、最后,我们在补充写一个部署脚本 ./script/deployPriceFeed.js:

const { computeAddress } = require("ethers/lib/utils");
const { ethers } = require("hardhat");  //ethers.js

async function deployContract(){
    const priceFeed = await ethers.getContractFactory("PriceFeed");
    const priceFeedContract = await priceFeed.deploy();
    await priceFeedContract.deployed();
    console.log("合约部署成功!")

    const contractAddress = await priceFeedContract.address;
    console.log("合约部署的地址为:", contractAddress );
}

deployContract().then(() => {
    process.exit(0);
}).catch(err => {
    console.error(err);
})

使用hardhat运行上面的js脚本,以完成合约部署:

PS E:\Study-Code\blockchain\DataFeedDemo> npx hardhat run script/deployPriceFeed.js --network goerli
合约部署成功!
合约部署的地址为 0x864b92DB4902a2C575C07b79518Ae50155D63577

访问链接如下:https://goerli.etherscan.io/address/0x864b92db4902a2c575c07b79518ae50155d63577

至此,hardhat框架的简单使用就完成了:合约开发——单元测试——部署发布!

jiguiquan@163.com

文章作者信息...

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐