Blog About
Table of Contents
  • เนื้อหา
  • Overview
  • Installation
  • Runing tasks
  • Compiling your contracts
  • Testing your contracts
  • Deploying your contracts
  • อ่านเพิ่ม

ลองใช้ hardhat กัน

Apisit N.
18 Sep 2022

อยากลองเขียน smart contract แบบ run บนเครื่องตัวเอง หลังจากไล่ดูไล่อ่านจากหลายๆที่ก็ไปไปเจอ Hardhat นี้แหละที่เขาใช้กัน หลายๆที่จะแนะนำให้ใช้ Remix ในการเขียน ข้อดีของ Remix คือมันเป็น online ใช้บน browser ได้เลยไม่ต้องติดตั้งโปรแกรมอะไรทั้งนั้น แต่ผมต้องการ run บนเครื่องตัวเองจะได้หาข้อมูลเรื่องอื่นๆนอกจาก coding ด้วย

เนื้อหาในบทความนี้จะเอามาจาก Documentation ของเว็บ hardhat.org

เนื้อหา

  • เนื้อหา
  • Overview
  • Installation
  • Runing tasks
  • Compiling your contracts
  • Testing your contracts
  • Deploying your contracts
  • อ่านเพิ่ม

Overview

Hardhat เป็นเครื่องมือที่เราจะใช้เพื่อเขียนหรือพัฒนา smart contract สำหรับ Ethereum software ทำให้เราสามารถ compiling, debugging และ deploying ตัว smart contract ของเราได้

Hardhat Runner คือ main หรือเป็นส่วนประกอบหลักเมื่อเราใช้งาน Hadhat ออกแบบมาด้วย concepts ของ tasks และ plugins ทุกครั้งที่เราสั่ง Hardhat จาก command-line หมายความว่าเรากำลังเรียกใช้ task เช่น npx hardhat compile ใช้คำสั่ง built-in compile task

Installation

ก่อนติดตั้งจะต้องสร้างโฟลเดอร์โปรเจคเปล่าๆขึ้นมาก่อน

Terminal window
mkdir my-project
cd my-project

เมื่อพร้อมแล้วสั่ง npx hardhat เพื่อสร้าง project

Terminal window
npx hardhat

TIP

ถ้าใช้ Windows แนะนำให้ติดตั้งและใช้ command ผ่าน WSL2

Terminal window
$ 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.11.2 👷‍
? What do you want to do? …
❯ Create a JavaScript project
Create a TypeScript project
Create an empty hardhat.config.js
Quit

เลือกสร้างโปรเจคเป็น JavaScript หรือ TypeScript

Runing tasks

เมื่อสร้างเสร็จแล้ว ลองใช้ npx hardhat คำสั่งเดิมดูว่า มีคำสั่ง built-in อะไรให้ใช้บ้าง

Terminal window
$ npx hardhat
Hardhat version 2.11.2
Usage: hardhat [GLOBAL OPTIONS] <TASK> [TASK OPTIONS]
GLOBAL OPTIONS:
--config A Hardhat config file.
--emoji Use emoji in messages.
--flamegraph Generate a flamegraph of your Hardhat tasks
--help Shows this message, or a task's help if its name is provided
--max-memory The maximum amount of memory that Hardhat can use.
--network The network to connect to.
--show-stack-traces Show stack traces (always enabled on CI servers).
--tsconfig A TypeScript config file.
--typecheck Enable TypeScript type-checking of your scripts/tests
--verbose Enables Hardhat verbose logging
--version Shows hardhat's version.
AVAILABLE TASKS:
check Check whatever you need
clean Clears the cache and deletes all artifacts
compile Compiles the entire project, building all artifacts
console Opens a hardhat console
coverage Generates a code coverage report for tests
flatten Flattens and prints contracts and their dependencies
gas-reporter:merge
help Prints this message
node Starts a JSON-RPC server on top of Hardhat Network
run Runs a user-defined script after compiling the project
test Runs mocha tests
typechain Generate Typechain typings for compiled contracts
verify Verifies contract on Etherscan
To get help for a specific task run: npx hardhat help [task]

คำสั่งเหล่านี้คือ built-in จาก Hardhat แต่เราสามารถติดตั้ง plugins เพิ่มได้ หรือจะเพิ่มคำสั่งแบบ custom เองก็ได้แต่จะยังไม่เขียนไว้ตรงนี้ว่าเพิ่มยังไง

Compiling your contracts

ต่อมาลองดูที่โฟลเดอร์ contracts/ จะเห็นไฟล์ Lock.sol เป็นตัวอย่าง smart contract ที่เขียนด้วยภาษา solidity เราจะลองสั่ง compile กัน

ยังไม่ต้องสนใจตัวโค้ด อยากให้ดูการใช้คำสั่งของ hardhat ก่อน

1
// SPDX-License-Identifier: UNLICENSED
2
pragma solidity ^0.8.9;
3
4
// Uncomment this line to use console.log
5
// import "hardhat/console.sol";
6
7
contract Lock {
8
uint public unlockTime;
9
address payable public owner;
10
11
event Withdrawal(uint amount, uint when);
12
13
constructor(uint _unlockTime) payable {
14
require(
15
block.timestamp < _unlockTime,
16
"Unlock time should be in the future"
17
);
18
19
unlockTime = _unlockTime;
20
owner = payable(msg.sender);
21
}
22
23
function withdraw() public {
24
// Uncomment this line, and the import of "hardhat/console.sol", to print a log in your terminal
25
// console.log("Unlock time is %o and block timestamp is %o", unlockTime, block.timestamp);
26
27
require(block.timestamp >= unlockTime, "You can't withdraw yet");
28
require(msg.sender == owner, "You aren't the owner");
29
30
emit Withdrawal(address(this).balance, block.timestamp);
31
32
owner.transfer(address(this).balance);
33
}
34
}

ลอง compile ด้วยคำสั่ง

Terminal window
npx hardhat compile

หลังจาก compile จะเห็นว่ามีโฟลเดอร์ใหม่ชื่อ artifacts, cache ถูกสร้างขึ้นมา

Terminal window
npx hardhat compile
Compiled 1 Solidity file successfully

Testing your contracts

ปกติแล้วหลังจากโปรแกรมเมอร์เขียนโค้ดเสร็จ ต่อมาก็ต้องลอง test กันหน่อย โดยตัวโปรเจคได้ใช้ lib ชื่อ Mocha, Chai, Ethers.js เพื่อใช้เขียน test

ลองดูที่โฟลเดอร์ test/ จะมีไฟล์ Lock.js

1
const {
2
time,
3
loadFixture,
4
} = require("@nomicfoundation/hardhat-network-helpers");
5
const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");
6
const { expect } = require("chai");
7
8
describe("Lock", function () {
9
// We define a fixture to reuse the same setup in every test.
10
// We use loadFixture to run this setup once, snapshot that state,
11
// and reset Hardhat Network to that snapshot in every test.
12
async function deployOneYearLockFixture() {
13
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
14
const ONE_GWEI = 1_000_000_000;
15
16
const lockedAmount = ONE_GWEI;
17
const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;
18
19
// Contracts are deployed using the first signer/account by default
20
const [owner, otherAccount] = await ethers.getSigners();
21
22
const Lock = await ethers.getContractFactory("Lock");
23
const lock = await Lock.deploy(unlockTime, { value: lockedAmount });
24
25
return { lock, unlockTime, lockedAmount, owner, otherAccount };
26
}
27
28
describe("Deployment", function () {
29
it("Should set the right unlockTime", async function () {
30
const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);
31
32
expect(await lock.unlockTime()).to.equal(unlockTime);
33
});
34
35
it("Should set the right owner", async function () {
36
const { lock, owner } = await loadFixture(deployOneYearLockFixture);
37
38
expect(await lock.owner()).to.equal(owner.address);
39
});
40
41
it("Should receive and store the funds to lock", async function () {
42
const { lock, lockedAmount } = await loadFixture(
43
deployOneYearLockFixture
44
);
45
46
expect(await ethers.provider.getBalance(lock.address)).to.equal(
47
lockedAmount
48
);
49
});
50
51
it("Should fail if the unlockTime is not in the future", async function () {
52
// We don't use the fixture here because we want a different deployment
53
const latestTime = await time.latest();
54
const Lock = await ethers.getContractFactory("Lock");
55
await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith(
56
"Unlock time should be in the future"
57
);
58
});
59
});
60
61
describe("Withdrawals", function () {
62
describe("Validations", function () {
63
it("Should revert with the right error if called too soon", async function () {
64
const { lock } = await loadFixture(deployOneYearLockFixture);
65
66
await expect(lock.withdraw()).to.be.revertedWith(
67
"You can't withdraw yet"
68
);
69
});
70
71
it("Should revert with the right error if called from another account", async function () {
72
const { lock, unlockTime, otherAccount } = await loadFixture(
73
deployOneYearLockFixture
74
);
75
76
// We can increase the time in Hardhat Network
77
await time.increaseTo(unlockTime);
78
79
// We use lock.connect() to send a transaction from another account
80
await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith(
81
"You aren't the owner"
82
);
83
});
84
85
it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () {
86
const { lock, unlockTime } = await loadFixture(
87
deployOneYearLockFixture
88
);
89
90
// Transactions are sent using the first signer by default
91
await time.increaseTo(unlockTime);
92
93
await expect(lock.withdraw()).not.to.be.reverted;
94
});
95
});
96
97
describe("Events", function () {
98
it("Should emit an event on withdrawals", async function () {
99
const { lock, unlockTime, lockedAmount } = await loadFixture(
100
deployOneYearLockFixture
101
);
102
103
await time.increaseTo(unlockTime);
104
105
await expect(lock.withdraw())
106
.to.emit(lock, "Withdrawal")
107
.withArgs(lockedAmount, anyValue); // We accept any value as `when` arg
108
});
109
});
110
111
describe("Transfers", function () {
112
it("Should transfer the funds to the owner", async function () {
113
const { lock, unlockTime, lockedAmount, owner } = await loadFixture(
114
deployOneYearLockFixture
115
);
116
117
await time.increaseTo(unlockTime);
118
119
await expect(lock.withdraw()).to.changeEtherBalances(
120
[owner, lock],
121
[lockedAmount, -lockedAmount]
122
);
123
});
124
});
125
});
126
});

เราสามารถสั่ง npx hardhat test เพื่อทดสอบตาม script ได้เลย ตัว Hardhat จะไปหาไฟล์ที่อยู่ในโฟลเดอร์ test/ ให้เอง

Terminal window
npx hardhat test
Lock
Deployment
✔ Should set the right unlockTime (1058ms)
✔ Should set the right owner
✔ Should receive and store the funds to lock
✔ Should fail if the unlockTime is not in the future (38ms)
Withdrawals
Validations
✔ Should revert with the right error if called too soon
✔ Should revert with the right error if called from another account
✔ Shouldn't fail if the unlockTime has arrived and the owner calls it
Events
✔ Should emit an event on withdrawals
Transfers
✔ Should transfer the funds to the owner
9 passing (1s)

Deploying your contracts

ต่อมา deploy ตัว contract โดยใช้ Hardhat script

ในโฟลเดอร์ scripts/ จะมีไฟล์ชื่อ deploy.js

1
// We require the Hardhat Runtime Environment explicitly here. This is optional
2
// but useful for running the script in a standalone fashion through `node <script>`.
3
//
4
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
5
// will compile your contracts, add the Hardhat Runtime Environment's members to the
6
// global scope, and execute the script.
7
const hre = require("hardhat");
8
9
async function main() {
10
const currentTimestampInSeconds = Math.round(Date.now() / 1000);
11
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
12
const unlockTime = currentTimestampInSeconds + ONE_YEAR_IN_SECS;
13
14
const lockedAmount = hre.ethers.utils.parseEther("1");
15
16
const Lock = await hre.ethers.getContractFactory("Lock");
17
const lock = await Lock.deploy(unlockTime, { value: lockedAmount });
18
19
await lock.deployed();
20
21
console.log(
22
`Lock with 1 ETH and unlock timestamp ${unlockTime} deployed to ${lock.address}`
23
);
24
}
25
26
// We recommend this pattern to be able to use async/await everywhere
27
// and properly handle errors.
28
main().catch((error) => {
29
console.error(error);
30
process.exitCode = 1;
31
});

เราสามารถสั่ง npx hardhat run เพื่อ deploy contract

Terminal window
$ npx hardhat run scripts/deploy.js
Lock with 1 ETH and unlock timestamp 1695031063 deployed to 0x5FbDB2315678afecb367f032d93F642f64180aa3

จากทั้งหมดที่ผ่านมา จะเห็นได้ว่าไม่มีการ run node เพื่อสร้าง blockchain network อะไรแบบนั้นเลยแทบจะไม่ต้องทำอะไรที่เกี่ยวกับ infra ของ blockchain ก็สามารถเขียนและทดสอบ smart contract ได้แล้วโดยใช้แค่ Hardhat

อ่านเพิ่ม

  • https://hardhat.org/hardhat-runner/docs/getting-started
© 2025 Apisit N.