赞
踩
我们必须建立属于自己的测试基础设施
,使用hardhat框架
hardhat
是最容易的,也是目前智能合约开发中最受欢迎的框架之一,被价值数十亿美元的大规模协议使
用,
是一种开发环境,允许基于JavaScript的开发,提供了很多工具,扩展性强,可调试
文件夹:hardhat-simple-storage-fcc
官网地址:hardhat.org
根据官网的教程操作:
https://hardhat.org/tutorial/testing-contracts
大概步骤: yarn init yarn add --dev hardhat yarn add --dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomicfoundation/hardhat-ethers @nomicfoundation/hardhat-verify chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v6 yarn hardhat 选择:Create a JavaScript project
1、粘贴simpleStorage.sol
,编译:yarn hardhat compile
错误:
Your project is an ESM project (you have "type": "module" set in your package.json) but your Hardhat config file uses the .js extension.
解决方式:
将chai降级为:"chai": "^4.3.7" 依赖项依赖于ES模块 yarn add chai@^4.3.7
node_moudules
有些是以@开始的,有些不是:
清楚的明白哪些包是官方的,哪些不是
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; //稳定的版本,MIT是限制最少的License contract SimpleStorage { uint256 public favoriteNum; People[] public people; //部署后的输入框表示想要获取到的数组中元素的索引 struct People { uint256 favoriteNum; string name; } function addPerson1(string memory _name, uint256 _favoriteNum) public { people.push(People(_favoriteNum, _name)); } function addPerson2(string memory _name, uint256 _favoriteNum) public { People memory newPerson = People({ favoriteNum: _favoriteNum, name: _name }); people.push(newPerson); } function store(uint256 _favoriteNum) public virtual { favoriteNum = _favoriteNum; } function retrieve() public view returns (uint256) { return favoriteNum; } }
yarn hardhat compile
// 之前 // const {ethers} = require("ethers") hardhat无法知道不同的代码中有不同的合约工厂,还没动,等我多用几次 // hardhat-ethers是一个将ethers内置进了hardhat的一个封装包 // 允许在不同脚本和其他情况下追踪不同的部署 const { ethers } = require("hardhat"); async function main() { // 如果使用ethers,无法知道SimpleStorage.sol已经编译好放在artifacts里面 // hardhat知道contracts文件夹的存在的,也知道他已经被编译好了 const SimpleStorageFactory = await ethers.getContractFactory( "SimpleStorage" ); console.log("deploy ...."); // 会直接部署到本地的hardhat节点上 const simpleStorage = await SimpleStorageFactory.deploy(); await simpleStorage.waitForDeployment(); console.log(simpleStorage.runner.address); console.log(simpleStorage); } main() .then(() => process.exit(0)) .catch((error) => { console.log(error); process.exit(1); });
yarn hardhat run scripts/deploy.js
其中target
表示合约的部署地址
HardhatEthersSigner 是一个对象,表示与以太坊网络交互的签名者。它包含以下属性: _gasLimit: 一个整数,表示交易的 gas 限制。在这个例子中,它的值为 30000000。 address: 一个字符串,表示签名者的地址。在这个例子中,它的值为 '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'。 provider: 一个 HardhatEthersProvider 对象,表示与以太坊网络交互的提供者。它包含以下属性: _hardhatProvider: 一个 LazyInitializationProviderAdapter 对象,表示与 Hardhat 框架交互的适配器。 _networkName: 一个字符串,表示网络的名称。在这个例子中,它的值为 'hardhat'。 _blockListeners: 一个空数组,表示监听区块事件的监听器。 _transactionHashListeners: 一个空 Map 对象,表示监听交易哈希事件的监听器。 _eventListeners: 一个空数组,表示监听事件的监听器。
小技巧:
使用ctrl + P
调出搜索框,输入>
调出命令面板,不输入>
搜索项目中文件
在写js
的时候分号很麻烦,我们可以添加prettier
和solidity prettier
插件
yarn add prettier preitter-plugin-solidity
创建.prettierrc
{ "tabWidth": 4 , "useTabs": false, "semi": false, "singleQuote": false }
hardhat内置了一个hardhat network
,是一个专门为开发设计的以太坊节点
打开hardhat.config.js
,添加有关默认网络的信息,虚拟的hardhat network
会自动提供RPC URL
和私钥,
require("@nomicfoundation/hardhat-toolbox"); /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { defaultNetwork: "hardhat", solidity: "0.8.19", }; // 有了网络标识后,在不同链,不同私钥之间的切换就会非常的容易
创建.env
添加环境变量
输入我们的SEPOLIA_RPC_URL
和PRIVATE_KEY
我们将从环境变量中获取:
调用dotenv
yarn add --dev dotenv
,添加require("dotenv").config()
启动配置
const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL; const PRIVATE_KEY = process.env.PRIVATE_KEY;
每一个EVM
基础网络都有一个新的chainID
,chainlist.org
Sepolia 58008
修改后:
require("@nomicfoundation/hardhat-toolbox"); require("dotenv").config() /** @type import('hardhat/config').HardhatUserConfig */ const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL; const PRIVATE_KEY = process.env.PRIVATE_KEY; module.exports = { defaultNetwork: "hardhat", networks: { sepolia: { /**需要告诉hardhhat如何连接到Sepolia网络 */ url: SEPOLIA_RPC_URL, account: [PRIVATE_KEY], chainId: 11155111, }, }, solidity: "0.8.19", };
结果:
结果:https://sepolia.otterscan.io/address/0x6ee82245c4a9D9Ad4A673Aa42aa4bb1e5224E4cC
编程方式验证合约:
VAWNN-MJAVB-QTBUT-A4SHJ
https://sepolia.etherscan.io/address/0x6ee82245c4a9D9Ad4A673Aa42aa4bb1e5224E4cC
合约没有被验证,回到部署脚本改进使得合约在不受后可以自动验证:
etherscan.io
和大多数区块链浏览器,它们的网站上有一个API
文档,是我们通过编程和etherscan
进行交互和操作的方式,通过API验证我们的合约
yarn add --dev @nomicfoundation/hardhat-verify
API key
:相当于允许我们使用etherscan API
的密码
注册登录etherscan.io
,点击API KEYS
,选择添加,APP name
设置成HH-FCC
,复制key
require("@nomicfoundation/hardhat-toolbox"); require("dotenv").config(); require("@nomicfoundation/hardhat-verify") /** @type import('hardhat/config').HardhatUserConfig */ const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL; const PRIVATE_KEY = process.env.PRIVATE_KEY; const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY; module.exports = { defaultNetwork: "hardhat", networks: { sepolia: { url: SEPOLIA_RPC_URL, accounts: [PRIVATE_KEY], chainId: 11155111, }, }, solidity: "0.8.19", etherscan: { apiKey: ETHERSCAN_API_KEY, } };
// 之前 // const {ethers} = require("ethers") // hardhat-ethers是一个将ethers内置进了hardhat的一个封装包 const { ethers, run } = require("hardhat"); async function main() { const SimpleStorageFactory = await ethers.getContractFactory( "SimpleStorage" ); console.log("deploy ...."); const simpleStorage = await SimpleStorageFactory.deploy(); await simpleStorage.waitForDeployment(); console.log(simpleStorage.runner.address); console.log(simpleStorage); } // 合约地址以及合约需要的一些参数,构造函数 async function vertify(contractAddress, args) { console.log("Verify contract ...."); await run("verity"); } main() .then(() => process.exit(0)) .catch((error) => { console.log(error); process.exit(1); });
const { ethers, run, network } = require("hardhat"); console.log(network.config); 执行: yarn hardhat run scripts/deploy.js
结果:显示了我们所在网络hardhat的大量信息
通过chainID
确定测试网络和实时网络
console.log(network.config); if (network.config.chainId === 11155111 && process.env.ETHERSCAN_API_KEY) { //等待 await simpleStorage.deploymentTransaction().wait(6); await vertify(simpleStorage.target, []) } }
部署到测试网络验证报错:
https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify
使用命令行也是:
自定义hardhat任务
创建一个Tasks文件夹,创建block-numbers.js
const { task } = require("hardhat/config"); //添加进config task("block-number", "print the current block number").setAction( // 设置我们这个函数想要做的事情 async (taskArgs, hre) => { const blockNumber = await hre.ethers.provider.getBlockNumber(); console.log(`current block number:${blockNumber}`); } ) module.exports = {};
添加进config
:
require("./tasks/block-numbers");
每次脚本运行都会重置blockNumber
使用测试网络:
hardhat本地节点
yarn hardhat node
在本地网络上启动一个节点,和Ganache
一样
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
节点并没有运行于hardhat network
可以在开一个终端,运行yarn hardhat run scripts/deploy.js
,本地节点并没有发生变化
仍然在使用hardhat 运行环境,只不过不是默认的hardhat network,这个节点会在脚本执行结束之后仍然存在
networks: { sepolia: { url: SEPOLIA_RPC_URL, accounts: [PRIVATE_KEY], chainId: 11155111, }, locahost: { url: "http://127.0.0.1:8545/", chainId: 31337, } },
添加后,新开的终端输入yarn hardhat run scripts/deploy.js --network locahost
,就会发现另一个终端有日志输出了
hardhat控制台:
hardhat控制台是一个JavaScript环境,用于运行JavaScript命令和区块链网络进行交互
yarn hardhat console --network localhost
不需要导入任何东西,因为所有的东西都已经在控制台中自动导入了
在控制台上直接输入deploy.js
上的js命令
按两下ctrl + c
退出
运行测试:
hardhat的最大优点是非常适用于运行测试,我们所有的代码都将对所有人开源供其交互,并有可能被利用
rekt.news
介绍了大量黑客攻击事件以及他们是如何攻击的,以及智能合约中发生了什么才会导致这些攻击的发生
重命名为test-deploy.js
hardhat测试使用的是Mocha
框架,是一个基于JavaScript的框架用于运行我们的测试
支持现代编程语言进行测试的观点是:会有更大的灵活性对智能合约进行更多的测试和交互(大多数项目)
支持使用solidity进行测试的观点是:测试用的代码和编写智能合约用的代码相同
describe
是一个关键字,hardhat的Mocha框架会识别,有两个参数,第一个接收的是字符串,第二个接收的是函数(通用的是匿名函数)
// 编写一个基本的测试 const { ethers } = require("hardhat"); const { assert, expect } = require("chai"); describe("SimpleStorage", () => { let simpleStorageFactory, simpleStorage; //告诉我们在每一个each之前要做什么,一个,每个测试之前,都会部署beforeEach beforeEach(async function () { simpleStorageFactory = await hre.ethers.getContractFactory("SimpleStorage"); simpleStorage = await simpleStorageFactory.deploy(); }); // 多个,运行测试代码的地方 it("Should start a favorite number of 0", async function () { const currentValue = await simpleStorage.retrieve(); const expextValue = "0"; // assert // expect,它们都会从chai包中导进来 assert.equal(currentValue.toString(),expextValue); }); // 调用store的时候,我们希望favoriteNum更新 it("Should update when we call store", async function () { const expextValue = "7"; const transactionResponse = await simpleStorage.store(expextValue); await transactionResponse.wait(1); const currentValue = await simpleStorage.retrieve(); assert.equal(expextValue, currentValue.toString()); }) })
yarn hardhat test
函数实际上要消耗的Gas:
hardhat-gas-report
yarn add hardhat-gas-reporter --dev
在config中添加新的分支:
require("hardhat-gas-reporter") gasReporter: { //测试时运行 enabled: true },
运行yarn hardhat test
,会自动运行gasReporter
结果:
改进:
登录coinmarketcap.com/api/
,复制我们的密钥
gasReporter: { //测试时运行 enabled: true, outputFile: "gas-report.txt", noColors: true, currency: "CNY",//每个函数的成本 // 为了获取current,需要获取一个私钥 coinmarketcap: COINMARKETCAP_API_KEY },
环境变量的改进:
使变量始终处于被填充的状态
const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL || "https://eth-rinkeby"; const PRIVATE_KEY = process.env.PRIVATE_KEY || "0xkey"; const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY || "key"; const COINMARKETCAP_API_KEY = process.env.COINMARKETCAP_API_KEY || "key";
Solidity-coverage通过遍历我们的测试,计算我们有多少代码被测试实际覆盖
yarn add --dev solidity-coverage
,添加到config中
运行yarn hardhat coverage
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。