2022-10-13Web300
请注意,本文编写于 559 天前,最后修改于 559 天前,其中某些信息可能已经过时。

hardhat提供了很多我们在使用ethers.js时很难实现的工具或者快捷的方法

搭建项目

yarn hardhat

根据提示生成一个简单的框架模版 例如这样:

image.png

添加需要的依赖

{
  "name": "hardhat-simple-storage-fcc",
  "author": "maochaoying",
  "devDependencies": {
    "@nomiclabs/hardhat-ethers": "^2.0.4",
    "@nomiclabs/hardhat-etherscan": "^3.0.0",
    "@nomiclabs/hardhat-waffle": "^2.0.2",
    "chai": "^4.3.4",
    "ethereum-waffle": "^3.4.0",
    "ethers": "^5.5.3",
    "hardhat": "^2.8.3",
    "hardhat-gas-reporter": "^1.0.7",
    "solidity-coverage": "^0.7.18"
  },
  "dependencies": {
    "dotenv": "^14.2.0",
    "prettier-plugin-solidity": "^1.0.0-beta.19"
  },
  "scripts": {
    "lint": "yarn prettier --check .",
    "lint:fix": "yarn prettier --write ."
  }
}

solidity-coverage

hardhat提供了一个查看当前我们编写的测试代码的功能覆盖率的插件,叫做solidity-coverage,专门用于可靠性测试的代码覆盖率。

hardhat-gas-reporter

gas费是我们每一次与区块链交互的时候产生的一笔费用,每次我们读取数据,运算数据,修改数据的时候,都会产生一定数量的gas。

gas费是我们与区块链通信的过程中提交的费用,,我们通过一定的语法技巧能节约很多的gas,给用户操作节省gas费,一个优秀的项目应该避免大量gas的消耗。

hardhat-gas-reporter是hardhat的一个插件,专门用于查看每个单元测试所消耗的gas费用。

代码安全

为了防止privete_key暴露在代码里,我们需要将一些密钥抽离在.env文件中,并在.gitignore中排除它,而后在使用处添加npm包dotenv进行引入。

例如:

PRIVATE_KEY=234523425asdfasdfa
GOERLI_RPC_URL=http://0.0.0.0:8545
COINMARKETCAP_API_KEY=asdfasdfasdfasdfasdfasdfasdf
ETHERSCAN_API_KEY=asdfasdfasdfs

SimpleStorage合约编写 .sol

// SPDX-License-Identifier: MIT

pragma solidity 0.8.8;

// pragma solidity ^0.8.0;
// pragma solidity >=0.8.0 <0.9.0;

contract SimpleStorage {
  uint256 favoriteNumber;

  struct People {
    uint256 favoriteNumber;
    string name;
  }

  // uint256[] public anArray;
  People[] public people;

  mapping(string => uint256) public nameToFavoriteNumber;

  function store(uint256 _favoriteNumber) public {
    favoriteNumber = _favoriteNumber;
  }

  function retrieve() public view returns (uint256) {
    return favoriteNumber;
  }

  function addPerson(string memory _name, uint256 _favoriteNumber) public {
    people.push(People(_favoriteNumber, _name));
    nameToFavoriteNumber[_name] = _favoriteNumber;
  }
}

scripts(deploy.js)

hardhat中内置了ethers对象(ethers.js库旨在为以太坊区块链及其生态系统提供一个小而完整的 JavaScript API 库 )、network对象(包括网络的一些常用参数)、run函数(执行hardhat的任务)。

  1. 通过ethers对象获取合约工厂并进行部署
  2. 通过network.config.chainId区分链接的网络,并确保ETHERSCAN_API_KEY存在
  3. 使用verify函数,自动验证并发布源码到ether scan
// imports
const { ethers, run, network } = require("hardhat")

// async main
async function main() {
  const SimpleStorageFactory = await ethers.getContractFactory("SimpleStorage")
  console.log("Deploying contract...")
  const simpleStorage = await SimpleStorageFactory.deploy()
  await simpleStorage.deployed()
  console.log(`Deployed contract to: ${simpleStorage.address}`)
  // what happens when we deploy to our hardhat network?
  if (network.config.chainId === 5 && process.env.ETHERSCAN_API_KEY) {
    console.log("Waiting for block confirmations...")
    await simpleStorage.deployTransaction.wait(6)
    await verify(simpleStorage.address, [])
  }

  const currentValue = await simpleStorage.retrieve()
  console.log(`Current Value is: ${currentValue}`)

  // Update the current value
  const transactionResponse = await simpleStorage.store(7)
  await transactionResponse.wait(1)
  const updatedValue = await simpleStorage.retrieve()
  console.log(`Updated Value is: ${updatedValue}`)
}

// async function verify(contractAddress, args) {
const verify = async (contractAddress, args) => {
  console.log("Verifying contract...")
  try {
    await run("verify:verify", {
      address: contractAddress,
      constructorArguments: args,
    })
  } catch (e) {
    if (e.message.toLowerCase().includes("already verified")) {
      console.log("Already Verified!")
    } else {
      console.log(e)
    }
  }
}

// main
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error)
    process.exit(1)
  })

初试task编写

  1. 第一个参数是命令名称
  2. 第二个参数是描述
  3. 通过setAction设置执行后的具体行为
const { task } = require("hardhat/config")

task("block-number", "Prints the current block number").setAction(
  // const blockTask = async function() => {}
  // async function blockTask() {}
  async (taskArgs, hre) => {
    const blockNumber = await hre.ethers.provider.getBlockNumber()
    console.log(`Current block number: ${blockNumber}`)
  }
)

module.exports = {}

hardhat.config.js

进行全局配置和引入一些插件

require("@nomiclabs/hardhat-waffle")
require("hardhat-gas-reporter")
require("./tasks/block-number")
require("@nomiclabs/hardhat-etherscan")
require("dotenv").config()
require("solidity-coverage")
// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more
/**
 * @type import('hardhat/config').HardhatUserConfig
 */

const COINMARKETCAP_API_KEY = process.env.COINMARKETCAP_API_KEY || ""
const GOERLI_RPC_URL =
  process.env.GOERLI_RPC_URL ||
  "https://eth-goerli.alchemyapi.io/v2/your-api-key"
const PRIVATE_KEY =
  process.env.PRIVATE_KEY ||
  "0x11ee3108a03081fe260ecdc106554d09d9d1209bcafd46942b10e02943effc4a"
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY || ""

module.exports = {
  defaultNetwork: "hardhat",
  networks: {
    hardhat: {},
    goerli: {
      url: GOERLI_RPC_URL,
      accounts: [PRIVATE_KEY],
      chainId: 5,
    },
    localhost: {
      url: "http://localhost:8545",
      chainId: 31337,
    },
  },
  solidity: "0.8.8",
  etherscan: {
    apiKey: ETHERSCAN_API_KEY,
  },
  gasReporter: {
    enabled: true,
    currency: "USD",
    outputFile: "gas-report.txt",
    noColors: true,
    coinmarketcap: COINMARKETCAP_API_KEY,
  },
}

使用chai进行测试

const { ethers } = require("hardhat")
const { expect, assert } = require("chai")

// describe("SimpleStorage", () => {})
describe("SimpleStorage", function () {
  // let simpleStorageFactory
  // let simpleStorage
  let simpleStorageFactory, simpleStorage
  beforeEach(async function () {
    simpleStorageFactory = await ethers.getContractFactory("SimpleStorage")
    simpleStorage = await simpleStorageFactory.deploy()
  })

  it("Should start with a favorite number of 0", async function () {
    const currentValue = await simpleStorage.retrieve()
    const expectedValue = "0"
    // assert
    // expect
    assert.equal(currentValue.toString(), expectedValue)
    // expect(currentValue.toString()).to.equal(expectedValue)
  })
  it("Should update when we call store", async function () {
    const expectedValue = "7"
    const transactionResponse = await simpleStorage.store(expectedValue)
    await transactionResponse.wait(1)

    const currentValue = await simpleStorage.retrieve()
    assert.equal(currentValue.toString(), expectedValue)
  })

  // Extra - this is not in the video
  it("Should work correctly with the people struct and array", async function () {
    const expectedPersonName = "Patrick"
    const expectedFavoriteNumber = "16"
    const transactionResponse = await simpleStorage.addPerson(
      expectedPersonName,
      expectedFavoriteNumber
    )
    await transactionResponse.wait(1)
    const { favoriteNumber, name } = await simpleStorage.people(0)
    // We could also do it like this
    // const person = await simpleStorage.people(0)
    // const favNumber = person.favoriteNumber
    // const pName = person.name

    assert.equal(name, expectedPersonName)
    assert.equal(favoriteNumber, expectedFavoriteNumber)
  })
})

本文作者:前端小毛

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!