一、关于预言机
1、为什么要使用预言机?
因为区块链的智能合约,是在沙箱中运行的,是没有办法获取区块链以外的数据的,外部API提供的数据和任何其他链下资源都是无法被区块链获取的;
如果在区块链中运行的程序(智能合约)能够从外部直接获取数据,调用API接口,那么智能合约将变得毫无意义,里面到处都是从外部API获取的可变数据,那么区块链的不可篡改性也将丢失。所以,沙箱机制是很有必要的!
但是有些场景下,我们智能合约程序运行时,不得不从外部(如互联网)获取最新数据,如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 交易对为例:
记住这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、部署合约,并进行测试:
需要注意的是,在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
这里我们选择创建一个空的 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框架的简单使用就完成了:合约开发——单元测试——部署发布!