以太坊智能合約開發框架:Hardhat v2 核心功能從入門到基礎教程

一、設置項目

Hardhat 項目是安裝了 hardhat 包并包含 hardhat.config.js 文件的 Node.js 項目。

操作步驟:

①初始化 npm

npm init -y

②安裝 Hardhat

npm install --save-dev hardhat

③創建 Hardhat 項目

npx hardhat init

在這里插入圖片描述

  1. 如果選擇 Create an empty hardhat.config.js ,Hardhat 會生成如下配置文件:

    /** @type import('hardhat/config').HardhatUserConfig */  
    module.exports = {  solidity: "0.8.28",  
    };  
    
  2. Hardhat 默認是支持 Ethers,如果使用 Viem 可選擇 Create a TypeScript project(with Viem)

  3. 如果選擇 Create a JavaScript projectCreate a TypeScript projectCreate a TypeScript project(with Viem),向導會詢問幾個問題,隨后創建目錄、文件并安裝依賴。其中最重要的依賴是 Hardhat Toolbox ,它集成了Hardhat 所需的所有核心插件。

這里選擇 Create a JavaScript project

在這里插入圖片描述

初始化后的項目結構如下:

contracts/  
ignition/modules/  
test/  
hardhat.config.js  

這是 Hardhat 項目的默認路徑:

  • contracts/:存放合約源碼
  • ignition/modules/:存放處理合約部署的 Ignition 模塊
  • test/:存放測試文件

如需修改路徑,可查看路徑配置文檔

二、VS Code 插件

Hardhat for Visual Studio Code 是官方 VS Code擴展,為 VS Code 提供 Solidity 高級支持。

在這里插入圖片描述

三、Hardhat 架構

Hardhat 是圍繞 任務插件 的概念設計的,Hardhat 的大部分功能來自插件。

1.任務

每次從命令行運行 Hardhat 命令時,都在運行一項任務。

查看項目中當前可用的任務

npx hardhat

在這里插入圖片描述

Hardhat 內置的常用任務:

  • npx hardhat compile:編譯Solidity合約代碼
  • npx hardhat test:運行測試腳本
  • npx hardhat run [path/to/script.js]:運行一個腳本
  • npx hardhat clean:清除構建輸出和緩存文件
  • npx hardhat console:交互式控制臺
  • npx hardhat node:啟動本地開發節點

2.插件

Hardhat 的大部分功能來自插件,可在 Plugins 列表中查看官方推薦。

使用插件的步驟如下:

①安裝插件

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

②在 hardhat.config.js 文件中引入插件

require("@nomicfoundation/hardhat-toolbox");  module.exports = {  solidity: "0.8.28",  
}; 

四、網絡

1.Hardhat 內置網絡

內置的 Hardhat Network 作為開發測試網絡,可搭配 Hardhat Network Helpers 庫控制網絡狀態,這樣更靈活。

啟動 HardHat 網絡節點

npx hardhat node  

在這里插入圖片描述

2.其他網絡

Hardhat 默認網絡是 Hardhat Network,如需使用其他網絡(如以太坊測試網、主網或其他節點軟件),可在 hardhat.config.js 導出對象的 networks 配置中進行設置,這是 Hardhat 項目管理網絡配置的方式。

①配置外部網絡(例如本地Geth)

module.exports = {networks: {geth: {url: "http://127.0.0.1:8545",accounts: ['你的私鑰1', '你的私鑰2', ...]  },},
}

②使用外部網絡

通過 --network 命令行參數可快速切換網絡

npx hardhat [任務] --network [網絡名]  

如果不加 --network [網絡名] 則將默認網絡作為任務網絡

3.更改默認網絡

如果要切換默認網絡,可在 hardhat.config.js 導出對象的 defaultNetwork 配置中進行設置(前提得先定義好 networks )。

module.exports = {networks: {geth: {url: "http://127.0.0.1:8545",accounts: ['私鑰1', '私鑰2', ...]  },},// defaultNetwork: "geth",  // 默認網絡切換成 geth,但開發測試還是使用 hardhat 網絡比較好
}

五、編寫合約

contracts目錄下編寫 Lock 合約

contracts/Lock.sol

// SPDX許可標識符: 未經許可
pragma solidity ^0.8.28;contract Lock {// 公開狀態變量:解鎖時間戳 & 合約所有者地址uint public unlockTime;address payable public owner;// 定義提款事件(提款金額、操作時間)event Withdrawal(uint amount, uint when);// 構造函數:接收解鎖時間并驗證有效性// 必須附帶 ETH 存款(payable 修飾)constructor(uint _unlockTime) payable {// 檢查解鎖時間是否在未來require(block.timestamp < _unlockTime,"Unlock time should be in the future");unlockTime = _unlockTime;owner = payable(msg.sender); // 設置部署者為所有者}// 提款函數(僅限所有者調用)function withdraw() public {// 驗證當前時間是否已到解鎖時間require(block.timestamp >= unlockTime, "You can't withdraw yet");// 驗證調用者是否為所有者require(msg.sender == owner, "You aren't the owner");// 觸發提款事件(合約余額、當前時間)emit Withdrawal(address(this).balance, block.timestamp);// 向所有者轉賬全部余額owner.transfer(address(this).balance);}
}

六、編譯合約

1.執行編譯任務

使用 Hardhat 內置的 compile 任務來編譯合約

npx hardhat compile

在這里插入圖片描述

這將會把 contracts/ 目錄下的所有合約進行編譯,編譯后會自動生成 artifacts 目錄,并自動將編譯相關的信息放在 artifacts/ 目錄下。

在這里插入圖片描述

后期如果僅修改了一個文件,那么只會重新編譯該文件以及受其影響的其他文件。

這是因為 Hardhat 有緩存機制。Hardhat會將每個智能合約的編譯結果緩存起來,以便在后續的編譯過程中重復使用。

這意味著如果您沒有對合約進行任何更改,Hardhat將直接從緩存中讀取編譯結果,而不需要重新編譯整個合約。如果對合約部分修改,那么會差量編譯。這樣可以極大地減少編譯時間,特別是在項目中存在多個合約的情況下。

但如果想強制進行編譯,可以使用 --force 參數,或者運行 npx hardhat clean 來清除緩存并刪除編譯產物。

# 清除編譯緩存
npx hardhat clean
# 強制編譯
npx hardhat compile --force

2.配置編譯器

如果需要自定義 Solidity 編譯器選項,可以通過 hardhat.config.js 中的 solidity 字段來實現。使用該字段最簡單的方式是通過簡寫形式來設置編譯器版本。Hardhat 會自動下載所設置的 solc(solidity 編譯器) 版本。

module.exports = {solidity: "0.8.28",
};

如果不指定版本則以 Hardhat 指定的默認版本

建議自定義版本,因為如果后期隨著 Solidity 新版本發布, Hardhat 官方可能會修改默認的編譯器版本 ,從而導致項目出現意外行為或編譯錯誤

注意:合約的版本與配置的編譯器版本不兼容,Hardhat 將會拋出錯誤。

3.更多編譯器相關配置

module.exports = {solidity: {version: "0.8.28",settings: {optimizer: {enabled: true,runs: 1000,},evmVersion: 'london'},},
};

settings 的結構與可以傳遞給編譯器的輸入 JSON 中的 settings 條目相同。一些常用的設置如下:

  • optimizer:一個包含enabledruns鍵的對象。默認值為{ enabled: false, runs: 200 }
  • evmVersion:一個字符串,用于控制目標 EVM 版本。例如:istanbulberlinlondon。默認值由solc 管理。

七、測試合約

本指南將介紹在 Hardhat 中測試合約的推薦方法。該方法借助 ethers 庫連接到 Hardhat 網絡,使用 MochaChai 進行測試。同時,還會用到自定義的 Chai 匹配器以及 Hardhat 網絡輔助工具,從而更輕松地編寫簡潔的測試代碼。這些工具包均屬于 Hardhat Toolbox 插件的一部分。

雖然這是推薦的測試設置方式,但 Hardhat 具有很高的靈活性:可以對該方法進行自定義,也可以采用其他工具開辟全新的測試路徑。

1.測試工具

1.1. mocha

Mocha 是一個能夠運行在 Node.js瀏覽器 中的多功能 JavaScript 測試框架,它讓異步測試變得 簡單有趣。Mocha 順序運行測試,并給出靈活而精確的報告,同時能夠將未捕獲的異常映射到準確的測試用例上。

1??describe 是一個 Mocha 函數,可組織測試

  • 參數: 接收測試組織名稱和回調函數。回調必須定義該部分的測試。這個回調不能是異步函數。
  • 全Mocha函數在全局范圍內可用、組織好測試可以讓調試變得更容易。
describe("學習mocha測試", function () {// 里面裝測試函數it
})

2??it 是另一個 Mocha 函數。可定義每個測試單元

  • 參數:接收單元測試名稱和回調函數。
  • 如果回調函數是異步的,Mocha 將自動 “await” 它
describe("Mocha測試", function () {it("測試單元1", async function () {// 具體的測試})it("測試單元2", async function () {// 具體的測試})
})

3??beforeEach 是 Mocha 中 describe 函數的一個鉤子。可在執行 describe 函數中 it 函數 之前執行

describe("Mocha測試", function () {beforeEach(async function () {// 在執行 it 函數前做些什么});it("測試單元1", async function () {// 具體的測試})it("測試單元2", async function () {// 具體的測試})
})  

1.2 chai

chai 是一個可以在 node瀏覽器 環境運行的BDD/TDD斷言庫,可以和任何 JavaScript 測試框架結合。在 Hardhat 中對其進行了加強,使得 chain 更符合合約的測試。學習Chai斷言庫

2.測試變量

測試內容:

  • 部署 Lock 合約
  • 斷言unlockTime() 返回的解鎖時間與在構造函數中傳入的時間一致

測試準備:

  • 查看 contracts/Lock.sol 合約代碼,了解邏輯

  • 在 test 目錄下新建 myLock.js 測試文件,編寫測試代碼

測試步驟:

①導入所需的測試工具:

  • chai 中導入 expect 函數用于編寫斷言
  • 導入 Hardhat 運行時環境(hre
  • 與 Hardhat 網絡交互的網絡輔助工具
const { expect } = require("chai");
const hre = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");

②使用 describeit 函數,它們是Mocha的全局函數,用于描述和分組測試

const { expect } = require("chai");
const hre = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");describe("Lock", function () {it("應設置正確的解鎖時間", async function () {// 測試代碼位于 it 函數的回調參數內。});
});

③編寫部署合約的邏輯

首先,設置要鎖定的金額(以 wei 為單位)和解鎖時間。對于解鎖時間,使用 time.latest 這個網絡輔助工具,它會返回最后一個已挖出區塊的時間戳。然后,部署合約:調用ethers.deployContract,傳入要部署的合約名稱和包含解鎖時間的構造函數參數數組。再傳入一個包含交易參數的對象,這是可選的,但通過設置其 value 字段來發送一些 ETH。

const { expect } = require("chai");
const hre = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");describe("Lock", function () {it("應設置正確的解鎖時間", async function () {const lockedAmount = 1_000_000_000;const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;// 部署一個可以提取資金的鎖定合約// 未來一年const lock = await hre.ethers.deployContract("Lock", [unlockTime], {value: lockedAmount,});});
});

④測試合約變量

檢查合約中 unlockTime() getter 方法返回的值是否與部署時使用的值相匹配。
由于調用合約上的所有函數都是異步的,必須使用 await 關鍵字來獲取其值;否則,將比較一個 Promise 和一個數字,這肯定會失敗。

const { expect } = require("chai");
const hre = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");describe("Lock", function () {it("應設置正確的解鎖時間", async function () {const lockedAmount = 1_000_000_000;const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;// 部署一個可以提取資金的鎖定合約// 未來一年const lock = await hre.ethers.deployContract("Lock", [unlockTime], {value: lockedAmount,});// 斷言該值是正確的expect(await lock.unlockTime()).to.equal(unlockTime);});
});

3.測試回滾函數

在之前的測試中,檢查了一個 getter 函數是否返回了正確的值。這是一個只讀函數,可以免費調用且沒有任何風險。

然而,其他函數可能會修改合約的狀態,且再修改狀態之前會有一些前置檢查,比如 Lock 合約中的 withdraw 函數。這意味著希望在調用這個函數之前滿足一些前置條件。如果查看該函數的前幾行,會看到有幾個 require檢查用于此目的:

contracts/Lock.sol

function withdraw() public {require(block.timestamp >= unlockTime, "You can't withdraw yet");require(msg.sender == owner, "You aren't the owner");
}

第一條語句檢查是否已達到解鎖時間,第二條語句檢查調用合約的地址是否為合約所有者。

為第一個前置條件編寫測試:

it("如果調用過快,應返回正確的錯誤", async function () {const lockedAmount = 1_000_000_000;const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;// 部署一個可以提取資金的鎖定合約// 未來一年const lock = await hre.ethers.deployContract("Lock", [unlockTime], {value: lockedAmount,})await expect(lock.withdraw()).to.be.revertedWith("You can't withdraw yet");
});

在之前的測試中,使用了 .to.equal,這是 Chai 的一部分,用于比較兩個值。在這里,使用 .to.be.revertedWith,它用于斷言交易將回滾,并且回滾的原因字符串等于給定的字符串。 .to.be.revertedWith 匹配器并非 Chai 本身的一部分,而是由 Hardhat Chai Matchers 插件添加的

注意,在之前的測試中寫的是 expect(await ...),但現在是 await expect(...)。在第一種情況下,是以同步方式比較兩個值;內部的 await 只是為了等待獲取值。在第二種情況下,整個斷言是異步的,因為它必須等待交易被挖出。這意味著 expect 調用返回一個 Promise,必須對其使用 await

4.操縱網絡時間

部署的 Lock 合約的解鎖時間為一年。如果想編寫一個測試來檢查解鎖時間過后會發生什么,顯然不能真的等上一年。可以使用更短的解鎖時間,比如 5 秒,但這不是一個很現實的值,而且在測試中等待 5 秒仍然很長。

解決辦法是模擬時間的流逝。這可以通過 time.increaseTo 網絡輔助工具來實現,它會挖出一個帶有給定時間戳的新區塊:

it("應將資金轉移給所有者", async function () {const lockedAmount = 1_000_000_000;const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;// 部署一個可以提取資金的鎖定合約// 未來一年const lock = await hre.ethers.deployContract("Lock", [unlockTime], {value: lockedAmount,})// 生成指定時間戳的最新區塊await time.increaseTo(unlockTime);// 如果交易回滾,這里會拋出錯誤await lock.withdraw();
});

如前所述,調用 lock.withdraw() 會返回一個 Promise。如果交易失敗,該 Promise 將被拒絕。使用 await 在這種情況下會拋出錯誤,所以如果交易回滾,測試將失敗。

5.使用不同的賬戶

withdraw 函數進行的第二個檢查是調用該函數的地址是否為合約所有者。默認情況下,部署和函數調用是使用第一個配置的賬戶進行的。如果想檢查只有所有者才能調用某個函數,就需要使用不同的賬戶,并驗證調用會失敗。

ethers.getSigners() 會返回一個包含所有配置賬戶的數組。可以使用合約的 .connect 方法,用不同的賬戶調用函數,并檢查交易是否回滾:

  it("如果從其他帳戶調用,應返回正確的錯誤", async function () {const lockedAmount = 1_000_000_000;const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;// 部署一個可以提取資金的鎖定合約// 未來一年const lock = await hre.ethers.deployContract("Lock", [unlockTime], {value: lockedAmount,})// 獲取賬戶列表,并解構const [owner, otherAccount] = await hre.ethers.getSigners();// 增加鏈上的時間以通過第一個檢查await time.increaseTo(unlockTime);// 使用lock.connect()從另一個賬戶發送交易await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith("You aren't the owner");});

這里再次調用一個函數,并斷言它會以正確的原因字符串回滾。不同之處在于用 .connect(anotherAccount) 從不同的地址調用該方法。

6.使用固定裝置(Fixtures)

到目前為止,在每個測試中都部署了 Lock 合約。這意味著在每個測試開始時,都必須獲取合約工廠,然后部署合約。對于單個合約來說,這可能沒問題,但如果設置更復雜,每個測試開始時都會有幾行代碼只是為了設置所需的狀態,而且大多數時候這些代碼都是相同的。

在典型的 Mocha 測試中,這種代碼重復問題可以通過beforeEach鉤子來處理。

const hre = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");describe("Lock", function () {let lock: any;let unlockTime: number;let lockedAmount = 1_000_000_000;beforeEach(async function () {const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;lock = await hre.ethers.deployContract("Lock", [unlockTime], {value: lockedAmount,});});it("some test", async function () {// 使用已部署的合約});
});

然而,這種方法有兩個問題:

  1. 如果需要部署多個合約,測試會變慢,因為每個測試作為設置的一部分都要發送多個交易。
  2. beforeEach 鉤子和測試之間像這樣共享變量既不美觀又容易出錯。

Hardhat 網絡輔助工具中的 loadFixture 助手解決了這兩個問題。這個助手接收一個固定裝置(fixture),即一個將鏈設置到所需狀態的函數。第一次調用 loadFixture 時,會執行該固定裝置函數。但第二次調用時, loadFixture 不會再次執行固定裝置函數,而是將網絡狀態重置到固定裝置函數執行后的狀態。這樣更快,并且會撤銷前一個測試所做的任何狀態更改。

const { expect } = require("chai");
const hre = require("hardhat");
const { time, loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers");describe("Lock", function () {// 定義固定裝置函數async function deployOneYearLockFixture() {const lockedAmount = 1_000_000_000;const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;const lock = await hre.ethers.deployContract("Lock", [unlockTime], {value: lockedAmount,});return { lock, unlockTime, lockedAmount };}it("應設置正確的解鎖時間", async function () {// 使用固定裝置(第一次使用,初始化執行一次deployOneYearLockFixture函數,并記錄當前區塊), 并獲取合約實例和解鎖時間const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);// 斷言值是正確的expect(await lock.unlockTime()).to.equal(unlockTime);});it("如果調用過快,應返回正確的錯誤", async function () {// 使用固定裝置(第二次使用,回滾區塊到初始化deployOneYearLockFixture函數所記錄的區塊), 并獲取合約實例和解鎖時間const { lock } = await loadFixture(deployOneYearLockFixture);await expect(lock.withdraw()).to.be.revertedWith("You can't withdraw yet");});it("應將資金轉移給所有者", async function () {// 使用固定裝置(第二次使用,回滾區塊到初始化deployOneYearLockFixture函數所記錄的區塊), 并獲取合約實例和解鎖時間const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);// 生成指定時間戳的最新區塊await time.increaseTo(unlockTime);// 如果交易回滾,這里會拋出錯誤await lock.withdraw();});it("如果從其他帳戶調用,應返回正確的錯誤", async function () {// 使用固定裝置(第二次使用,回滾區塊到初始化deployOneYearLockFixture函數所記錄的區塊), 并獲取合約實例和解鎖時間const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);// 獲取賬戶列表,并解構const [owner, otherAccount] = await hre.ethers.getSigners();// 增加鏈上的時間以通過第一個檢查await time.increaseTo(unlockTime);// 使用lock.connect()從另一個賬戶發送交易await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith("You aren't the owner");});
});

固定裝置函數可以返回任何想要的值,loadFixture 助手會返回該值。建議像這里一樣返回一個對象,這樣就可以提取出該測試中關心的值。

7.使用調試

Hardhat Network 允許通過從 Solidity 代碼調用來打印日志記錄消息和合約變量

使用步驟:

①在合約中導入 hardhat 的 console.sol

import "hardhat/console.sol";

②在合約中使用console.log()

// SPDX許可標識符: 未經許可
pragma solidity ^0.8.28;// 導入 console.sol 庫
import "hardhat/console.sol";contract Lock {// 公開狀態變量:解鎖時間戳 & 合約所有者地址uint public unlockTime;address payable public owner;// 定義提款事件(提款金額、操作時間)event Withdrawal(uint amount, uint when);// 構造函數:接收解鎖時間并驗證有效性// 必須附帶 ETH 存款(payable 修飾)constructor(uint _unlockTime) payable {// 檢查解鎖時間是否在未來require(block.timestamp < _unlockTime,"Unlock time should be in the future");unlockTime = _unlockTime;owner = payable(msg.sender); // 設置部署者為所有者}// 提款函數(僅限所有者調用)function withdraw() public {// 調試console.log("unlockTime %d,currentTimestamp %d", unlockTime, block.timestamp);// 驗證當前時間是否已到解鎖時間require(block.timestamp >= unlockTime, "You can't withdraw yet");// 驗證調用者是否為所有者require(msg.sender == owner, "You aren't the owner");// 觸發提款事件(合約余額、當前時間)emit Withdrawal(address(this).balance, block.timestamp);// 向所有者轉賬全部余額owner.transfer(address(this).balance);}
}

8.執行測試任務

使用 Hardhat 內置的 test 任務來執行測試腳本

npx hardhat test

這將會執行 test/ 目錄下所有測試腳本

在這里插入圖片描述

很明顯第一個單元測試時間花費最長 522ms ,這是因為初始化執行 fixture函數。

9.其他測試

9.1 測量測試覆蓋率

Hardhat Toolbox 包含 solidity-coverage 插件,用于測量項目的測試覆蓋率。只需運行 coverage 任務,就會得到一份報告:

npx hardhat coverage

在這里插入圖片描述

9.2 使用 gas 報告器

Hardhat Toolbox 還包含 hardhat-gas-reporter 插件,根據測試執行情況獲取使用了多少 gas 的指標。當執行test任務且設置了 REPORT_GAS 環境變量時,會運行 gas 報告器:

REPORT_GAS=true npx hardhat test

對于 Windows 用戶,在 PowerShell 會話中設置環境變量的命令是$env:REPORT_GAS="true"

$env:REPORT_GAS="true"; npx hardhat test

八、部署合約

1.編寫部署模塊

若要部署合約,可使用聲明式部署系統 Hardhat Ignition

新建負責部署 Lock 合約的 Ignition 模塊 LockModule 位于 ignition/modules 目錄下

ignition/modules/Lock.js

const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");  const JAN_1ST_2030 = 1893456000;  // 默認值為 2030 年 1 月 1 日,時間戳`1893456000`
const ONE_GWEI = 1_000_000_000n;  // 默認值為 1 Gwei,即`1_000_000_000n`module.exports = buildModule("LockModule", (m) => {  const unlockTime = m.getParameter("unlockTime", JAN_1ST_2030);  const lockedAmount = m.getParameter("lockedAmount", ONE_GWEI);  const lock = m.contract("Lock", [unlockTime], {  value: lockedAmount,  });  return { lock };  
});  

2.執行部署任務

npx hardhat ignition deploy ./ignition/modules/你的部署模塊文件名.js --network <網絡名>  

若未指定網絡,Hardhat Ignition 將部署到 hardhat.config.js 配置的默認網絡。

1??部署到 Hardhat 網絡

npx hardhat ignition deploy ./ignition/modules/Lock.js

在這里插入圖片描述

2??指定網絡部署

npx hardhat ignition deploy ./ignition/modules/Lock.js --network <網絡名>  

九、配置變量

1.為什么需要配置變量?

Hardhat 項目中使用配置變量,是為存儲用戶特定值、保護敏感數據(如 API 密鑰、私鑰等),便于項目共享協作,提高代碼可維護性與靈活性,以及適應不同環境。

注意:配置變量以明文形式存儲在磁盤上,請勿用于存儲不希望以未加密文件形式保存的數據。可通過 npx hardhat vars path 查看存儲文件位置。

2.配置變量和環境變量的區別

dotenv 環境變量:若使用 dotenv ,可能導致意外上傳 .env 文件導致敏感數據泄露。

vars 配置變量:Hardhat 項目可以將配置變量用于用戶特定的值或不應包含在代碼存儲庫中的數據。這些變量是通過作用域中的任務設置的,可以使用對象在配置中檢索。

3.在命令行中操作配置變量

1??設置配置變量(為配置變量賦值,不存在則創建)

npx hardhat vars set 配置變量名

2??獲取配置變量

npx hardhat vars get 配置變量名

3??查詢計算機上存儲的所有的配置變量

npx hardhat vars list

4??刪除配置變量語法

npx hardhat vars delete 配置變量名

4.在文件中使用配置變量

①導入 vars 實例

const { vars } = require("hardhat/config");

②使用配置變量

  1. 直接使用

    const { vars } = require("hardhat/config");const PRIVATE_KEY = vars.get("環境變量名");
    
  2. 使用配置變量并設置默認值(變量不存在時使用)

    const { vars } = require("hardhat/config");const PRIVATE_KEY = vars.get("環境變量名", "默認值");
    
  3. 先檢查再使用

    const { vars } = require("hardhat/config");const PRIVATE_KEY = vars.has("環境變量名") ? [vars.get("環境變量名")] : '備選值'; 
    

5.使用配置變量存儲私鑰

①先設置私鑰的配置變量

npx hardhat vars set PRIVATE_KEY

在這里插入圖片描述

可以在 hardhat.config.js 文件中檢索存儲的配置變量。

require("@nomicfoundation/hardhat-toolbox");
const { vars } = require("hardhat/config");const PRIVATE_KEY = vars.get("PRIVATE_KEY");/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {solidity: {version: "0.8.28",settings: {optimizer: {enabled: true,runs: 1000,},evmVersion: 'london'},},networks: {geth: {url: "http://127.0.0.1:8545",accounts: [PRIVATE_KEY]  },},// defaultNetwork: "geth",  // 默認網絡切換成 geth
};

十、驗證合約

驗證合約是指公開合約的源代碼以及所用的編譯器設置,這樣任何人都能編譯該代碼,并將生成的字節碼與鏈上部署的字節碼進行對比。在像以太坊這樣的開放平臺上,這一操作極其重要。

本指南將介紹如何在 Etherscan 瀏覽器中完成此操作。

提示:如果想驗證非 Hardhat Ignition 部署的合約,或者想在 Sourcify 而非 Etherscan 上驗證合約,可以使用 hardhat-verify 插件。

1.從 Etherscan 獲取 API 密鑰

操作步驟:

①首先,你需要從 Etherscan 獲取一個 API 密鑰。

具體操作如下:訪問 Etherscan 網站,登錄賬號(若沒有則需注冊),打開 “API Keys” 標簽頁,點擊 “Add” 按鈕,為你創建的 API 密鑰命名(例如 “Hardhat”),之后你會在列表中看到新創建的密鑰。

②將 Etherscan 的 API 密鑰存在配置變量中

npx hardhat vars set ETHERSCAN_API_KEY 

③在 Hardhat 的 hardhat.config.js 中 添加 etherscan 配置,并將 API 密鑰添加到這里

const ETHERSCAN_API_KEY = vars.get("ETHERSCAN_API_KEY");module.exports = {// ...其他配置...etherscan: {apiKey: ETHERSCAN_API_KEY,},
};

2.在 Sepolia 測試網部署并驗證合約

將使用 Sepolia 測試網來部署和驗證合約,因此需要在 Hardhat 配置文件中添加該網絡。這里使用 Infura 連接到網絡,如果愿意,也可以使用其他 JSON - RPC URL,如 Alchemy。

操作步驟:

①訪問 https://infura.io 注冊賬號,在其控制臺創建一個新的 API 密鑰

②將 INFURA 的 API 密鑰存儲到配置變量中

npx hardhat vars set INFURA_API_KEY

③在 Hardhat 的 hardhat.config.js networks 中添加 sepolia 網絡配置,并將 API 密鑰添加到這里

const INFURA_API_KEY = vars.get("INFURA_API_KEY");export default {// ...其他配置...networks: {sepolia: {url: `https://sepolia.infura.io/v3/${INFURA_API_KEY}`,accounts: [PRIVATE_KEY],},},
};

要在 Sepolia 上部署合約,需要向進行部署的地址發送一些 Sepolia 以太幣。可以從水龍頭獲取測試網以太幣,水龍頭是一種免費分發測試以太幣的服務。以下是一些 Sepolia 水龍頭:

  • Alchemy Sepolia Faucet
  • QuickNode Sepolia Faucet
  • Ethereum Ecosystem Sepolia Faucet

現在可以部署合約了,但在此之前,要讓合約的源代碼具有唯一性。

打開的合約文件,添加一條包含獨特信息的注釋,比如你的 GitHub 用戶名。請記住,在這里添加的任何內容都會和代碼的其他部分一樣,在 Etherscan 上公開可見:

contracts/Lock.sol

// Author: @你的名稱
contract Lock {

將利用在 “部署合約” 指南中創建的 Ignition 模塊 Lock 來進行部署。使用 Hardhat Ignition 和新添加的 Sepolia 網絡運行部署命令:

npx hardhat ignition deploy ignition/modules/Lock.js --network sepolia --deployment-id sepolia-deployment

提示:--deployment-id 標志是可選的,但它允許你為部署指定一個自定義名稱。這樣在后續操作中,比如驗證合約時,引用起來會更方便。

最后,要驗證已部署的合約,你可以運行 ignition verify 任務并傳入部署 ID:

npx hardhat ignition verify sepolia-deployment

或者,你可以使用 --verify 標志調用 deploy 任務,將部署和驗證合并為一步:

npx hardhat ignition deploy ignition/modules/Lock.js --network sepolia --verify

提示:如果你收到錯誤信息,提示地址沒有字節碼,這可能意味著 Etherscan 尚未對合約進行索引。這種情況下,等待一分鐘后再試。

ignition verify 任務成功執行后,將看到一個指向你合約公開驗證代碼的鏈接。

十一、自定義任務

Hardhat 本質上是一個任務運行器,借助它能夠讓開發工作流程實現自動化。它自帶了像 compiletest 這類內置任務,同時也可以自行添加自定義任務。

1??編寫自定義任務

編寫不帶參數的基本任務,該任務會打印出可用賬戶的列表,同時探究其工作原理。

hardhat.config.js

require("@nomicfoundation/hardhat-toolbox");
const { vars } = require("hardhat/config");const PRIVATE_KEY = vars.get("PRIVATE_KEY");
const ETHERSCAN_API_KEY = vars.get("ETHERSCAN_API_KEY");
const INFURA_API_KEY = vars.get("INFURA_API_KEY");// 自定義任務
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {const accounts = await hre.ethers.getSigners();for (const account of accounts) {console.log(account.address);}
});/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {solidity: {version: "0.8.28",settings: {optimizer: {enabled: true,runs: 1000,},evmVersion: 'london'},},networks: {geth: {url: "http://127.0.0.1:8545",accounts: [PRIVATE_KEY]  },sepolia: {url: `https://sepolia.infura.io/v3/${INFURA_API_KEY}`,accounts: [PRIVATE_KEY],},},// defaultNetwork: "geth",  // 默認網絡切換成 gethetherscan: {apiKey: ETHERSCAN_API_KEY,},
};

task 函數來定義新任務。

  • 它的第一個參數是任務名稱,也就是在命令行中用來運行任務的名稱

  • 第二個參數是任務描述,當你使用 npx hardhat help 時會顯示該描述

  • 第三個參數是一個異步函數,在你運行任務時會執行這個函數。它接收兩個參數:

    1. 一個包含任務參數的對象。目前還沒有定義任何參數。

    2. Hardhat 運行時環境(HRE),它包含了 Hardhat 及其插件的所有功能。在任務執行期間,能發現它的所有屬性被注入到全局命名空間中。

在這個函數里,可以自由地實現任何功能。在這個例子中,使用 ethers.getSigners() 來獲取所有已配置的賬戶,并打印出每個賬戶的地址。

可以為任務添加參數,Hardhat 會幫你處理參數的解析和驗證。還可以覆蓋現有的任務,這樣就能改變 Hardhat 不同部分的工作方式。

2??執行自定義任務

npx hardhat accounts

在這里插入圖片描述

十二、Hardhat 控制臺

Hardhat 內置了一個交互式 JavaScript 控制臺。通過運行以下命令即可使用:

$ npx hardhat console  
Welcome to Node.js v12.10.0.  
Type ".help" for more information.  
>  

打開控制臺前會先調用 compile 任務,若需跳過可使用 --no-compile 參數

npx hardhat console --no-compile  

1.執行環境

控制臺的執行環境與任務、腳本和測試完全一致:配置已處理完畢,Hardhat 運行時環境(HRE)已初始化并注入全局作用域。

  • config:查看 Hardhat 配置對象

    > config  
    {  solidity: { compilers: [ [Object] ], overrides: {} },  defaultNetwork: 'hardhat',  ...  
    }  
    >  
    
  • ethers:若按入門指南操作或安裝了@nomicfoundation/hardhat-ethers

    > ethers  
    {  Signer: [Function: Signer] { isSigner: [Function] },  ...  provider: EthersProviderWrapper { ... },  getSigners: [Function: getSigners],  getContractAt: [Function: bound getContractAt] AsyncFunction  
    }  
    >  
    

所有注入到 HRE 中的內容都會自動在全局作用域中可用。如需顯式引用 HRE,也可通過require導入:

> const hre = require("hardhat")  
> hre.ethers  
{ /* 與上述ethers對象一致 */ }  

2.歷史記錄功能

控制臺支持大多數交互式終端的歷史記錄功能(包括跨會話記錄),可通過向上箭頭鍵查看歷史命令。本質上,Hardhat 控制臺是 Node.js 控制臺的實例,因此 Node.js 的所有功能均可在此使用。

3.異步操作與頂級 await

與以太坊網絡(及智能合約)的交互均為異步操作,因此大多數 API 和庫通過 JavaScript 的 Promise 返回值。
為簡化操作,Hardhat 控制臺支持頂級 await語句(例如直接使用await調用異步函數):

> console.log(await ethers.getSigners())  
[  Signer { address: '0xf39F...', provider: Provider },  Signer { address: '0x7099...', provider: Provider },  ...  
] 
  • config:查看 Hardhat 配置對象

    > config  
    {  solidity: { compilers: [ [Object] ], overrides: {} },  defaultNetwork: 'hardhat',  ...  
    }  
    >  
    
  • ethers:若按入門指南操作或安裝了@nomicfoundation/hardhat-ethers

    > ethers  
    {  Signer: [Function: Signer] { isSigner: [Function] },  ...  provider: EthersProviderWrapper { ... },  getSigners: [Function: getSigners],  getContractAt: [Function: bound getContractAt] AsyncFunction  
    }  
    >  
    

所有注入到 HRE 中的內容都會自動在全局作用域中可用。如需顯式引用 HRE,也可通過require導入:

> const hre = require("hardhat")  
> hre.ethers  
{ /* 與上述ethers對象一致 */ }  

2.歷史記錄功能

控制臺支持大多數交互式終端的歷史記錄功能(包括跨會話記錄),可通過向上箭頭鍵查看歷史命令。本質上,Hardhat 控制臺是 Node.js 控制臺的實例,因此 Node.js 的所有功能均可在此使用。

3.異步操作與頂級 await

與以太坊網絡(及智能合約)的交互均為異步操作,因此大多數 API 和庫通過 JavaScript 的 Promise 返回值。
為簡化操作,Hardhat 控制臺支持頂級 await語句(例如直接使用await調用異步函數):

> console.log(await ethers.getSigners())  
[  Signer { address: '0xf39F...', provider: Provider },  Signer { address: '0x7099...', provider: Provider },  ...  
] 

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/79726.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/79726.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/79726.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

安卓基礎(無障礙點擊)

無障礙點擊核心代碼 // 自定義無障礙服務類&#xff0c;繼承自Android系統的AccessibilityService public class MyAccessibilityService extends AccessibilityService {// 當系統產生無障礙事件時的回調方法&#xff08;如界面變化、焦點切換等&#xff09;Overridepublic v…

阿里云服務遷移實戰: 05-OSS遷移

概述 Bucket 復制分為兩種&#xff0c;同區域復制和跨區域復制 同賬號復制比較簡單&#xff0c;根據提示填寫信息即可&#xff0c;本文主要介紹跨賬號復制。 同區域復制 授權角色選擇 “AliyunOSSRole”, 創建方法見 “跨區域復制”。然后點擊確定即可。 跨區域復制 假設我…

Qt 的信號與槽機制依賴元對象系統(Meta-Object System)實現

內部數據結構 在 Qt 中,信號和槽之間的連接主要通過 QObject 類及其相關的私有類進行管理。每個 QObject 實例都維護著一個指向其 QMetaObject 的指針,該對象包含了有關類的所有元信息,包括信號、槽等。此外,還有一個關鍵的數據結構用于存儲信號與槽之間的連接信息,即 Co…

前端面試寶典---性能優化

一、加載優化 1. 第三方模塊放在CDN 例如 leaflet通過cdn引入&#xff0c;這樣就不會占用打包體積了 2. prefetch 預加載 例如&#xff0c;之后馬上有個場景需要一個圖片&#xff0c;我們就可以通過link 的 prefetch 對資源進行預先加載 再例如&#xff0c;我們公司是無網絡開…

從零開始:Android Studio開發購物車(第二個實戰項目)

一年經驗的全棧程序員&#xff0c;目前頭發健在&#xff0c;但不知道能撐多久。 文章目錄 前言 一、頁面編寫 1. 頂部標簽欄title_shopping.xml 2. 商品展現列表activity_shopping_channel.xml 3. 商品詳情頁面activity_shopping_detail.xml 4. 購物車頁面activity_shopping…

PostgteSQL for Everybody基礎部分筆記

筆記分享內容參考密歇根大學 Charles Russell Severance 開設的PostgreSQL課程&#xff1a;postgresql-for-everybody&#xff0c;網址為&#xff1a;https://www.coursera.org/specializations/postgresql-for-everybody#courses&#xff0c;在B站等也有相關視頻分享。 我分享…

Python項目源碼63:病歷管理系統1.0(tkinter+sqlite3+matplotlib)

1.病歷管理系統包含以下主要功能&#xff1a; 核心功能&#xff1a;病歷信息錄入&#xff08;患者姓名、年齡、性別、診斷結果、主治醫生&#xff09;&#xff0c;自動記錄就診時間&#xff0c;病歷信息展示&#xff08;使用Treeview表格&#xff09;&#xff0c;病歷信息查詢…

MCP底層協議完整通信過程

2025 年是智能體的元年, 也注定是智能體集中爆發的一年! 兩個互聯領域的重大挑戰: 第一、 Agent 與 Tools (工具)的交互 Agent 需要調用外部工具和 API

docker:制作鏡像+上傳鏡像+拉取鏡像

1.dockerfile制作鏡像 示例內容&#xff1a; 1.創建一個index.js的文件 console.log("hello world")2.在相同目錄下創建名為dockerfile的文件 FROM node:alpine COPY index.js /index.js CMD node /index.js3.構建鏡像 docker build -t minterra/hello-docker . …

docker制作python大模型鏡像(miniconda環境),工程改造記錄

**環境說明&#xff1a;**從系統鏡像開始打造python大模型鏡像&#xff0c;之前是人工手動裝的方式&#xff0c;并且模型和依賴在公網中&#xff0c;對于離線交付環境不太友好&#xff0c;所以打造的離線化交付版本 Dockerfile: FROM centos:7.9 ENV PYTHONIOENCODINGutf-8 E…

Rust中避免過度使用鎖導致性能問題的策略

一、引言 在 Rust 多線程編程中&#xff0c;鎖是實現線程同步的重要工具&#xff0c;它可以防止多個線程同時訪問和修改共享數據&#xff0c;從而避免數據競爭和不一致的問題。然而&#xff0c;過度使用鎖會帶來嚴重的性能問題&#xff0c;如鎖競爭導致的線程阻塞、上下文切換…

數據結構每日一題day15(鏈表)★★★★★

題目描述&#xff1a;將一個帶頭結點的單鏈表A分解為兩個帶頭結點的單鏈表A和 B,使得A表中含有原表中序號為奇數的元素,而B表中含有原表中序號為偶數的元素,且保持相對順不變&#xff0c;最后返回 B 表。 算法思想&#xff1a; 1.初始化&#xff1a; 創建新鏈表 B 的頭結點。…

【雜談】-探索 NVIDIA Dynamo 的高性能架構

探索 NVIDIA Dynamo 的高性能架構 文章目錄 探索 NVIDIA Dynamo 的高性能架構1. 大規模人工智能推理的日益嚴峻的挑戰2. 使用 NVIDIA Dynamo 優化 AI 推理3. 實際應用和行業影響4. 競爭優勢&#xff1a;Dynamo 與其他方案對比5. 總結 隨著人工智能&#xff08;AI&#xff09;技…

postgresql數據庫基本操作

1. 連接 PostgreSQL 數據庫 首先&#xff0c;使用 psql 命令行工具連接到數據庫。如果是本地連接&#xff0c;命令格式如下&#xff1a; psql -U postgres -d <數據庫名稱> -h <主機地址>其中&#xff1a; -U postgres&#xff1a;表示以 postgres 用戶身份登錄…

工業大模型:從設備診斷到工藝重構

引言 工業大模型正在引發制造業認知革命。據埃森哲研究,到2026年全球工業大模型市場規模將突破280億美元,其中工藝優化應用占比達42%。本文將系統解析工業大模型的"預訓練-領域適配-應用落地"技術路徑,并通過設備健康診斷與工藝參數生成的實踐案例,展示如何構建…

PyQt5基本介紹

PyQt5是基于Digia公司強大圖形框架Qt5的python接口&#xff0c;由一組python模塊構成。是一個用于創建桌面應用程序的Python庫&#xff0c;它是Qt圖形用戶界面工具包的Python綁定。 Qt是一個跨平臺的C庫&#xff0c;提供了一套豐富的工具和功能&#xff0c;用于開發圖形用戶界…

Tire 樹(字典樹/前綴樹)

一、定義與結構 用來快速存儲查找字符串集合的一種數據結構 將字符串按順序連接根節點上&#xff0c;并在字符串結束的地方打上標記并計數。 二、模板題 acwing 835 Trie 樹的字符串統計 題目&#xff1a; 維護一個字符串集合&#xff0c;支持兩種操作&#xff1a; I x 向…

【時時三省】(C語言基礎)怎樣定義和引用一維數組

山不在高&#xff0c;有仙則名。水不在深&#xff0c;有龍則靈。 ----CSDN 時時三省 一維數組是數組中最簡單的&#xff0c;它的元素只需要用數組名加一個下標&#xff0c;就能唯一地確定。如上面介紹的學生成績數組s就是一維數組。有的數組&#xff0c;其元素要指定兩個下標才…

編譯faiss

編譯faiss-1.10.0 首先確保自己cmake的版本&#xff1a; cmake --version 確保其版本至少為CMake 3.24.0 or higher is required。 其次安裝OpenBLAS&#xff1a; https://github.com/OpenMathLib/OpenBLAS 去這里去安轉Openblas內容&#xff0c;然后確保自己的CPU的指令集是存…

Linux 入門:操作系統進程詳解

目錄 一.馮諾依曼體系結構 一&#xff09;. 軟件運行前為什么要先加載&#xff1f;程序運行之前在哪里&#xff1f; 二&#xff09;.理解數據流動 二.操作系統OS(Operator System) 一&#xff09;.概念 二&#xff09;.設計OS的目的 三&#xff09;.如何理解操作系統…