Hardhat 開發框架 - Solidity開發教程連載

Tiny熊發表於2023-05-11

Decert.me 要連載教程了, 《Solidity 開發教程》 力求系統深入的介紹 Solidity 開發, 同時這是一套互動式教程,你可以實時的修改教程裡的合約程式碼並執行。

本教程來自貢獻者 @Tiny熊,讓我們正式開始學習吧。

如果你已經是 Hardhat 的使用者,可以直接跳到文末,參與挑戰領取技能認證 NFT。


Hardhat 提供了一個靈活且易於使用的環境,可以輕鬆地編寫、測試和部署智慧合約。類似的開發工具或框架還有: Remix IDETruffle Foundry, 目前最受歡迎的是 Hardhat 與 Foundry。

Hardhat 使用 Node 進行包管理,如果你熟悉 Node 及 Javascript, Hardhat 將非常簡單上手。

Hardhat還內建了Hardhat 網路(Hardhat Node),它是為開發而設計的本地以太坊網路。 用來部署合約,執行測試和除錯程式碼

在本文中,我們將介紹:

  1. 建立及配置Hardhat專案
  2. 編寫智慧合約
  3. Hardhat 編譯合約
  4. 使用 Ethers.js 和為合約編寫自動化測試
  5. 使用 console.log()除錯 Solidity
  6. 使用 Hardhat 部署合約
  7. 使用 Hardhat Etherscan 進行開源驗證。
  8. Hardhat 外掛的使用

本文對應的程式碼在:https://github.com/xilibi2003/training_camp_2/tree/main/w1_hardhat

建立及配置Hardhat專案

Hardhat 構建在Node.js之上, 使用 Hardhat 要求我們在電腦先安裝好Node.js (>= 16.0), 環境準備可以參考這裡

先建立專案目錄:

mkdir hardhat-tutorial
cd hardhat-tutorial

初始化 Node 專案:

npm init

安裝 Hardhat :

npm install --save-dev hardhat

在安裝Hardhat的目錄下執行:

npx hardhat

使用鍵盤選擇"建立一個新的hardhat.config.js(Create a JavaScript project)" ,然後回車。

$ npx hardhat
888    888                      888 888               888
888    888                      888 888               888
888    888                      888 888               888
8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
888    888 .d888888 888    888  888 888  888 .d888888 888
888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888

? Welcome to Hardhat v2.13.0 ?‍

? What do you want to do? …
❯ Create a JavaScript project
  Create a TypeScript project
  Create an empty hardhat.config.js
  Quit

這個 JavaScript Hardhat 工程會預設下載 hardhat-toolbox 外掛及一些常規設定:

建立好的Hardhat工程包含檔案有:

  • contracts:智慧合約目錄
  • scripts :部署指令碼檔案
  • test:智慧合約測試用例資料夾。
  • hardhat.config.js:配置檔案,配置hardhat連線的網路及編譯選項。

編寫合約

合約開發推薦使用 VSCode 編輯器 + solidity 外掛,在contracts 下新建一個合約檔案 Counter.sol (*.sol 是 Solidity 合約檔案的字尾名), 複製如下程式碼:

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

contract Counter {
    uint counter;

    constructor() {
        counter = 0;
    }

    function count() public {
        counter = counter + 1;
    }

    function get() public view returns (uint) {
        return counter;
    }
}

接下來就可以編譯這個合約了。

使用OpenZepplin 等第三方庫

在編寫合約時,儘量不要重複造輪子,基於優質開源的第三方庫,不僅可以提交效率,還可以讓我們的合約程式碼更安全,例如要開發一個 Token,可以用npm 安裝OpenZepplin 庫:

npm install @openzeppelin/contracts --save-dev

然後在合約中 import 相應庫中的合約檔案及可。

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract Token is ERC20 {
  constructor(uint256 initialSupply) ERC20("Token Name", "Token Symbol") {
    _mint(msg.sender, initialSupply);
  }
}

編譯合約

hardhat.config.js 有預設的Solidity 編譯器配置:

require("@nomicfoundation/hardhat-toolbox");

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.18",
};

因此我們直接編譯合約即可,在終端中執行 npx hardhat compilecompile任務是內建任務之一。

$ npx hardhat compile
Compiling 1 file with 0.8.18
Compilation finished successfully

合約已成功編譯了。

成功編譯後,會在 artifacts/contracts/ 目錄下生成Counter.json 和 build-info, Counter.json包含了智慧合約的 ABI 、位元組碼(Bytecode)等。

:::tip

智慧合約的 ABI(Application Binary Interface)資訊,其中包括了合約的函式、事件等介面資訊。這個檔案通常會在與其他合約互動時使用,因為它可以被其他合約和 DApp 使用。

Bytecode 是部署合約所需的位元組碼(也稱為建立時位元組碼),部署合約時,就是把該位元組碼作為交易的輸入資料傳送鏈上。:::

:::

編寫測試用例

為智慧合約編寫自動化測試至關重要,因為事關使用者資金。

在我們的測試中,使用 Harhdat 內建的網路,使用ethers.js與前面的合約進行互動,並使用 Mocha 作為測試執行器。

在專案 test下,並建立一個名為Counter.js的新檔案:

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

let counter;

describe("Counter", function () {
  async function init() {
    const [owner, otherAccount] = await ethers.getSigners();
    const Counter = await ethers.getContractFactory("Counter");
    counter = await Counter.deploy();
    await counter.deployed();
    console.log("counter:" + counter.address);
  }

  before(async function () {
    await init();
  });

  // 
  it("init equal 0", async function () {
    expect(await counter.get()).to.equal(0);
  });

  it("add 1 equal 1", async function () {
    let tx = await counter.count();
    await tx.wait();
    expect(await counter.get()).to.equal(1);
  });

});

在終端上執行npx hardhat test。 你應該看到以下輸出:

> npx hardhat test


  Counter
counter:0x5FbDB2315678afecb367f032d93F642f64180aa3
    ✔ init equal 0
    ✔ add 1 equal 1

  2 passing (1s)

這意味著測試透過了。 現在我們解釋主要程式碼:

  const Counter = await ethers.getContractFactory("Counter");

ethers.js中的ContractFactory是用於部署新智慧合約的抽象,因此此處的Counter是用來例項合約的工廠。

counter = await Counter.deploy();

ContractFactory上呼叫deploy()將啟動部署,並返回解析為ContractPromise。 該物件包含了智慧合約所有函式的方法。

let tx = await counter.count();
await tx.wait();

counter 上呼叫合約方法, 並等待交易執行完畢。

注意,預設情況下, ContractFactoryContract例項連線到第一個簽名者(Singer)

若需要使用其他的簽名這, 可以使用合約例項connect 到另一個簽名者, 如 counter.connect(otherAccount)

expect(await counter.get()).to.equal(0);

判斷相等,我們使用Chai,這是一個斷言庫。 這些斷言函式稱為“匹配器”,在此實際上使用的“匹配器”來自Hardhat Chai Matchers

使用 Console.log 除錯合約

在**Hardhat Node **節點上執行合約和測試時,你可以在Solidity程式碼中呼叫console.log()列印日誌資訊和合約變數,可以方便我們除錯程式碼。

在合約程式碼中匯入**Hardhat **的console.log就可以使用它。

pragma solidity ^0.8.0;

import "hardhat/console.sol";

contract Counter {
    uint public counter;

    constructor(uint x) {
        counter = x;
    }

    function count() public {
        counter = counter + 1;
        console.log("counter is %s ", counter);
    }

}

就像在JavaScript中使用一樣, 將一些console.log新增函式中,執行測試時,將輸出日誌記錄:

> npx hardhat test

  Counter
counter:0x5FbDB2315678afecb367f032d93F642f64180aa3
    ✔ init equal 0
counter is 1
    ✔ add 1 equal 1 (38ms)


  2 passing (1s)

可以在這裡瞭解更多 console.log 。

部署合約

其實我們在測試時, 合約已經部署到了Hardhat 內建的網路上,部署合約我們需要編寫一個部署指令碼。

scripts資料夾,新建一個deploy.js 用來寫部署指令碼,部署指令碼其實和前面測試時 init 函式類似:

const { ethers } = require("hardhat");

async function main() {

   const Counter = await ethers.getContractFactory("Counter");
   const counter = await Counter.deploy();
   await counter.deployed();

  console.log("Counter address:", counter.address);
}

main();

執行 npx hardhat run scripts/deploy.js 時, 可以合約會部署到Hardhat 內建網路上。

> npx hardhat run scripts/deploy.js
Counter address: 0x5FbDB2315678afecb367f032d93F642f64180aa3

為了在執行任何任務時指示Hardhat連線到特定的EVM網路,可以使用--network引數。 像這樣:

npx hardhat run scripts/deploy.js --network <network-name>

network-name 需要在 hardhat.config.js 檔案中進行配置:

require("@nomicfoundation/hardhat-toolbox");

// 填入自己的私鑰或助記詞,
const PRIVATE_KEY1 = "0x.... YOUR PRIVATE KEY1";
const PRIVATE_KEY2 = "0x....  YOUR PRIVATE KEY1";
const Mnemonic = "YOUR Mnemonic";


module.exports = {
  solidity: "0.8.9", // solidity的編譯版本
  networks: {
    goerli: {
      url: "https://eth-goerli.api.onfinality.io/public",
      accounts: [PRIVATE_KEY1,PRIVATE_KEY2],
      chainId: 5,
    },
    
     mumbai: {
      url: "https://endpoints.omniatech.io/v1/matic/mumbai/public",
      accounts: {
        mnemonic: Mnemonic,
      },
      chainId: 80001,
    },
  }
};

以上配置了兩個網路,一個是以太坊測試網 goerli, 一個是 Polygon 測試網mumbai, 我們可以在 https://chainlist.org 找到每個網路的節點 URL 及 chainID。

在網路配置中,需要提供提交交易賬號, 可以透過私鑰或助記詞 進行配置,這裡配置的賬號(需要提前充幣進入到賬號中),在hardhat 指令碼中(測試及部署指令碼)呼叫getSigners 即可獲得:

const [owner, otherAccount] = await ethers.getSigners();

一個私鑰對應一個Singer,助記詞則對應無數個 Singer , 為每個專案生成一個獨立的賬號是比較推薦的做法,使用 ChainTool 開源工具 可以生成賬號。

:::tip

助記詞可以推匯出無數了私鑰,可參考:BIP39

:::

另外要注意, 在 Goerli 上進行部署,需要將Goerli-ETH傳送到將要進行部署的地址中。 可以從水龍頭免費或一些測試幣,這是Goerli的一個水龍頭:

最後執行:

npx hardhat run scripts/deploy.js --network goerli

如果一切順利,你應該看到已部署的合約地址。

程式碼開源驗證

智慧程式碼開源會增加了合約的透明度和可靠性,是專案建立信任很重要的一個步驟。

hardhat-toolbox 工具箱裡,包含了 hardhat-etherscan 外掛用於驗證已經部署到區塊鏈網路上的智慧合約程式碼與原始碼是否匹配,在完成驗證後在區塊鏈瀏覽器中合約標籤上會出現✅, 如圖:

image-20230313104044517

在部署智慧合約時,合約位元組碼會被寫入到區塊鏈中,這意味著其他人無法檢查合約的原始碼。程式碼驗證的過程是將已部署合約的位元組碼與原始Solidity程式碼再次編譯後與部署的位元組碼進行比較,確保它們是一致的。

相比在區塊鏈瀏覽器上上傳程式碼驗證, hardhat-etherscan 有很多優點,否則會自動使用 hardhat config 值設定的編譯器選項,並且當程式碼中引用的第三方庫或合約, hardhat-etherscan 能自動探測並處理。

開源驗證的步驟是:

  1. 安裝 hardhat-toolboxhardhat-etherscan , 這一步我們這裡已經完成,因為在初始化專案的時候安裝了 hardhat-toolbox , 如果沒有安裝,可以使用以下命令安裝

    npm install --save-dev @nomiclabs/hardhat-etherscan
    
  2. hardhat.config.js 中配置您的 Etherscan API 金鑰和網路設定,例如:

  require("@nomicfoundation/hardhat-toolbox");
  或
  // require("@nomiclabs/hardhat-etherscan");
  
  etherscan: {
    apiKey: ""
  },
  
 

如何獲取 Etherscan API 金鑰?

  1. 訪問部署網路對應主網的 Etherscan 網站,並註冊一個賬號(如果還沒有賬號的話)。
  2. 登入你的賬號並進入 Etherscan 的「我的帳戶」頁面。
  3. 點選頁面左側的「API-KEYs」標籤頁。
  4. 在頁面上方的「Create New API KEY」部分,輸入 API 金鑰的名稱和描述,然後選擇需要訪問的 API 許可權。
  5. 點選「Generate」按鈕來生成 API 金鑰。
  1. 執行驗證命令:

    npx hardhat verify <deployed-contract-address> "引數(若有)" --network <network-name> 
    

    例如,要在 goerli 網路上驗證合約,可以執行以下命令:

    npx hardhat verify 0x..... --network goerli
    

該命令會為我們上傳合約程式碼並驗證其原始碼。如果一切順利(網路順暢的話),在 Etherscan 上看到的合約被成功驗證。

Hardhat 外掛(Plugin)的使用

上面程式碼開源驗證時,使用了hardhat-etherscan外掛,其實也可以使用 hardhat-verify 外掛。

https://hardhat.org/hardhat-runner/plugins 連結可以找到當前使用較多的外掛,例如:hardhat-gas-reporter 可以對部署合約及函式執行的Gas消耗給出報告;solidity-coverage 可以對測試覆蓋率給出報告。

要使用一個外掛通常要:

  1. 用 Node.js 包管理先安裝相應的外掛

  2. hardhat.config.js 檔案中引入外掛,以便Hardhat 能載入上對應的外掛。

參考文件

示例非常簡單, 更多使用方法,可參考文件:

小結

本文介紹了 Hardhat 開發框架的一些基本概念和使用方法,瞭解瞭如何使用 Hardhat 進行合約編譯、部署、除錯及測試,在開發中要經常檢視文件,瞭解更多Hardhat 用法。


Hardhat 的使用你掌握了嗎?來這裡挑戰一下看看,挑戰完成你就可以領取到一枚技能認證 NFT。

碼一個未來


原教程連結:https://decert.me/tutorial/solidity/tools/hardhat
Decert.me -- 碼一個未來

相關文章