Published on
Blockchain

เริ่มต้นเขียน Solidity ด้วย Hardhat

hello-solidity-with-hardhat
Discord

ช่วงนี้ผมค่อนข้างอินกับ Smart Contract เพราะกำลังอยู่ในช่วงหัดเขียน หัดลองครับ ก็เลยลองเขียนเป็นบทความเผื่อใครหลายๆ คนที่สนใจเหมือนๆกัน ตัวผมเองก็ยังเป็นมือใหม่มากๆ

บทความนี้จะพาไปลองส่องตัวโปรเจ็ค hardhat ว่าตัว sample project ของ hardhat มีอะไรบ้าง และรู้จักกับ solidity คร่าวๆ วิธีการ deploy วิธีการเรียกใช้ hardhat

ซึ่งทั้งหมดยังเป็น local network นะครับ ยังไม่ได้ติดต่อ testnet หรือ mainnet แต่อย่างใด

Step 1 - สร้างโปรเจ็ค

ทำการสร้างโปรเจ็คขึ้นมา ผมตั้งชื่อว่า hello-solidity

mkdir hello-solidity
cd hello-sollidity

จากนั้น init project เพื่อให้มีไฟล์ package.json :

npm init -y
# หรือ yarn
yarn init -y

ติดตั้ง hardhat

npm install hardhat --save-dev
# Yarn
yarn add hardhat -D

เมื่อติดตั้ง hardhat เรียบร้อยแล้ว ก็ทำการสร้างโปรเจ็คด้วยคำสั่งของ hardhat

npx hardhat

# yarn
yarn hardhat
  1. เลือก Create a basic sample project
  2. Hardhat project root ? <enter> ถ้าไม่ต้องการเปลี่ยนตัวโปรเจ็คก็สร้างที่ root folder.
  3. Do you want to add a .gitignore? (Y/n) › y - เพื่อ generate ไฟล์ .gitignore
  4. Do you want to install this sample project's dependencies with yarn... - y เพื่อติดตั้ง dependencies ให้เลย ไม่งั้น ก็ต้องมา npm install ทีหลังอยู่ดี

ผลลัพธ์จะได้แบบนี้

yarn hardhat

yarn run v1.22.18
$ /Users/chai/dev/hello-solidity/node_modules/.bin/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.9.2 👷‍

✔ What do you want to do? · Create a basic sample project
✔ Hardhat project root: · /Users/chai/dev/hello-solidity
✔ Do you want to add a .gitignore? (Y/n) · y
✔ Do you want to install this sample project's dependencies with yarn (@nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers)? (Y/n) · y
...
...

✨ Project created ✨
See the README.md file for some example tasks you can run.
✨  Done in 48.59s.

Step 2 - Hardhat

มาลองสำรวจกันดีกว่าว่า Hardhat ให้อะไรเรามาบ้าง

จะเห็นว่า Hardhat ได้ทำการ generate ไฟล์และโฟลเดอร์ต่างๆ ให้เรา ดังนี้

tree -L 2 -I node_modules

├── README.md
├── contracts
│   └── Greeter.sol
├── hardhat.config.js
├── package.json
├── scripts
│   └── sample-script.js
├── test
│   └── sample-test.js
└── yarn.lock
  • contracts - เป็นไฟล์ Smart Contract ของเรานั่นเอง
  • scripts - เป็นไฟล์ javascript ที่เอาไว้ interact กับ smart contract (จริงๆ ชื่อ folder ไรก็ได้)
  • test - ไฟล์ test
  • hardhat.config.js - เป็นไฟล์ config หลัก ของ Hardhat เช่นกำหนด network, url หรือ account ต่างๆ

ลองดูไฟล์ contracts/Greeter.sol

Greeter.sol
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "hardhat/console.sol";

contract Greeter {
    string private greeting;

    constructor(string memory _greeting) {
        console.log("Deploying a Greeter with greeting:", _greeting);
        greeting = _greeting;
    }

    function greet() public view returns (string memory) {
        return greeting;
    }

    function setGreeting(string memory _greeting) public {
        console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
        greeting = _greeting;
    }
}
//SPDX-License-Identifier: Unlicense

บรรทัดแรก เป็นการกำหนด License ให้กับไฟล์ เช่น MIT, Unlicense

pragma solidity ^0.8.0;

pragma directive เพื่อบอกให้ compiler รู้ว่าจะ compile solidity เวอร์ชั่นอะไร ตัวเลข เป็นแบบ semver

contract Greeter {

}

ตัว contract ถ้าสังเกต จะเหมือนการสร้าง class เลย และจริงๆ ก็มอง contract เป็น class ก็ได้

constructor(string memory _greeting) {
    console.log("Deploying a Greeter with greeting:", _greeting);
    greeting = _greeting;
}

มี constructor เหมือนภาษา programming อื่นๆ ซึ่งตัว contructor จะถูกเรียกครั้งเดียว ในปกติ เวลาสร้าง class ตัว constructor จะถูกเรียกตอน new Class() ส่วนใน contract ตัว constructor จะถูกเรียกตอน deploy contract.

import "hardhat/console.sol";
console.log("");

เราสามารถใช้ console.log() ในภาษา solidity ได้ เพราะเรา import ตัว console.sol จาก hardhat มานั่นเอง

function greet() public view returns (string memory) {
    return greeting;
}

ฟังค์ชั่น greet() ทำการ return string กลับไป ก็เหมือน function ในภาษาอื่นๆ ต่างกันที่ syntax นิดหน่อย คือ

  • public - คือกำหนดให้เป็น public function ใครก็เรียกใช้ function นี้ได้
  • view - เพื่อบอกว่า function นี้จะไม่เปลี่ยนแปลงค่า state นะ (read only)
  • returns (string memory)- การ return จะเขียนในรูปแบบนี้ ซึ่งmemory` คือรูปแบบการเก็บข้อมูล
function setGreeting(string memory _greeting) public {
    console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
    greeting = _greeting;
}

function setGreeting รับค่า string จากนั้นทำการ set ค่า ให้ greeting ที่เป็น state variable ที่เรากำหนดไว้ใน contract.

string private greeting;

เมื่อไหร่ก็ตามที่ state variable มีการเปลี่ยนค่า คือการ write data ลงบน blockchain ก็จะเกิด transaction ขึ้น สังเกต เราเข้าถึง greeting ได้เลย ถ้าอย่าง JavaScript อาจต้องใช้ this.greeting ไรงี้

ต่อมา สังเกตไฟล์ scripts/sample-scripts.js

sample-scripts.js
const hre = require("hardhat");

async function main() {
  // Hardhat always runs the compile task when running scripts with its command
  // line interface.
  //
  // If this script is run directly using `node` you may want to call compile
  // manually to make sure everything is compiled
  // await hre.run('compile');

  // We get the contract to deploy
  const Greeter = await hre.ethers.getContractFactory("Greeter");
  const greeter = await Greeter.deploy("Hello, Hardhat!");

  await greeter.deployed();

  console.log("Greeter deployed to:", greeter.address);
}

สิ่งที่ได้รู้จากตัวอย่าง sample-scripts คือ

  • hardhat จะรัน hardhat compile ให้เราอัตโนมัติ ถ้าเราใช้คำสั่ง hardhat run (task)
  • hre (Hardhat Runtime Environment) ไม่จำเป็นต้อง import ถ้าเราใช้คำสั่ง npx hardhat ตัว hre จะเป็น global scope เข้าถึงได้เลย

ทดลอง compile โดยการเปิด Terminal ขึ้นมา

npx hardhat compile

ตัว Hardhat จะทำการ compile และ generate โฟลเดอร์ artifacts ขึ้นมา

├── artifacts
│   ├── build-info
│   ├── contracts
│   └── hardhat

กลับไปดู sample-scripts อีกครั้ง

//1. บรรทัดนี้ เป็นคำสั่งเพื่อสร้าง contract factory จาก contract ที่ชื่อ `Greeter`
const Greeter = await hre.ethers.getContractFactory('Greeter');

// 2. เมื่อได้ factory ก็ใช้คำสั่ง deploy จะเห็นว่า "Hello, Hardhat!" คือค่า argument ที่ถูกส่งไปใน contructor นั่นเอง
const greeter = await Greeter.deploy('Hello, Hardhat!');

// 3. เรียก `deployed()` เพื่อรอ transaction confirm เพราะข้อมูลที่ deploy มันคือการเซฟลง blockchain
await greeter.deployed();

สังเกตว่า ตัว syntax ในไฟล์นี้มันก็คือ JavaScript นี่แหละ หากใครไม่คุ้นชิน promise ลองอ่านเรื่อง async/await เพิ่มเติมครับ

สุดท้าย ทดสอบรัน sample-scripts ดูผลลัพธ์ดีกว่า

npx hardhat run scripts/sample-scripts.js

จะได้ผลลัพธ์เป็นแบบนี้ (ตัว address ไม่เหมือนกันนะ)

Deploying a Greeter with greeting: Hello, Hardhat!
Greeter deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3

Step 3 - Interact with Contract

หลังจากที่เราพอรู้ขั้นตอน deploy แล้ว ต่อมาลองเรียก greet() จากไฟล์ sample-scripts ทำได้แบบนี้เลย

const Greeter = await hre.ethers.getContractFactory('Greeter');
const greeter = await Greeter.deploy('Hello, Hardhat!');

await greeter.deployed();

const greet = await greeter.greet();
console.log('greeting : ', greet);

ผลลัพธ์

Deploying a Greeter with greeting: Hello, Hardhat!
Greeter deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
greeting :  Hello, Hardhat!

ลอง setGreeting() ดู ซึ่งการเปลี่ยน state หรือการเขียนข้อมูลลง blockchain มันต้องรอ transaction confirm ครับ ซึ่ง ตัว js ก็จะเป็นแบบนี้

const Greeter = await hre.ethers.getContractFactory('Greeter');
const greeter = await Greeter.deploy('Hello, Hardhat!');

await greeter.deployed();

console.log('Greeter deployed to:', greeter.address);

const greet = await greeter.greet();
console.log('greeting : ', greet);

const tx = await greeter.setGreeting('This is new greeting.');
tx.wait();

const newGreet = await greeter.greet();
console.log('greeting (new) : ', newGreet);

สังเกตว่า awaitreeter.setGreeting() จะได้เป็น transaction ที่ยังไม่ confirm ครับ ต้องใช้ tx.wait() เพื่อรอ transaction confirm นั่นเอง

ผลลัพธ์

Deploying a Greeter with greeting: Hello, Hardhat!
Greeter deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
greeting :  Hello, Hardhat!
Changing greeting from 'Hello, Hardhat!' to 'This is new greeting.'
greeting (new) :  This is new greeting.

สรุป

สำหรับบทความนี้ ก็เป็นพื้นฐาน เป็น Ovewview คร่าวๆ สำหรับคนที่สนใจ Solidity / Smart Contract นะครับ เรียกได้ว่าเขียนโค๊ดไม่กี่บรรทัดเอง ส่วนใหญ่เน้นทำความเข้าใจกับมันก่อน และการใช้ Hardhat ที่ค่อนข้างสะดวกและง่ายมากๆ ยังไง ก็ลองไปเล่น ลองฝึกเพิ่มเติมกันดูนะครับ เพราะอ่านอย่างเดียวก็ไม่ได้อะไร ต้องไปลองหัดทำ หัดประยุกต์ดูด้วยตัวเองครับ

Happy Coding

Authors
Discord