เขียน Solidity ..ชีวิตดีได้ด้วย Hardhat!

ชีวิตของ Solidity Developer ที่อยู่ได้ด้วย Remix นั้น ถึงจุด ๆ หนึ่งอาจจะนึกขึ้นมาว่า มันมีสิ่งที่ดีกว่านี้รอเราอยู่ไหมนะ? …บทความนี้จะมาตอบเลยว่า มี!

รูปปกเสกโดยดีไซเนอร์มือฉมังแห่ง Cleverse

จากบทความ Web 3.0 นั้น ผมได้มีการพูดถึง Smart Contract ไว้ว่าเป็นหนึ่งในอาวุธสำคัญที่จะทำให้เราเข้าใกล้โลก Web 3.0 เข้าไปอีกก้าว และเป็นฟีเจอร์หลักของ Ethereum เลยก็ว่าได้ ซึ่งสามารถเขียนโดยภาษา Solidity ผ่าน Tools ต่าง ๆ เช่น Remix หรือ Hardhat

Disclaimer: บทความนี้จะขอข้ามรายละเอียดทางเทคนิคหลายอย่างนะครับ โดยจะพยายามใส่ลิ้งก์ที่เกี่ยวข้องเผื่อศึกษาเพิ่มเติมให้ครบ
แล้วก็รวมถึง Technical Details ของ Solidity ที่ขอละไว้ ซึ่งตรงนี้คิดว่าหากอยากลงลึก ลองค้นหาคอร์สเรียน หรือถามอาจารย์ YouTube ดู น่าจะได้ความรู้ครบถ้วนกว่าเป็นบทความครับผม 🥰

ก่อนจะ Hardhat รู้จัก Remix สักนิด

Hardhat นั้นทำให้การสร้าง Smart Contract นั้น Developer ทำได้อย่างสะดวกสบายมากขึ้น

“สบายขึ้นนี่ คุณน้องเทียบกับอะไรฮะ?” — คำถามในใจผู้อ่าน

จริง ๆ แล้ว Ethereum.org เองเนี่ย ก็มี IDE สำหรับเขียน Solidity อยู่แล้วนะครับ ซึ่งก็คือ Remix

remix.ethereum.org

หากสนใจ Remix ลองเล่นดูโดยอ่าน Guide ละเอียดๆได้ที่นี่ แต่สำหรับบทความนี้ขอมูฟออนไปยัง Hardhat ดีกว่า

ทำไมถึงดีกว่า? ขอเล่าในหัวข้อถัดไปครับ

Hardhat!?

เป็น Development Environment ที่ทำให้เราสามารถ Compile, Deploy, Test, Debug ตัว Smart Contract ได้ ซึ่งมาพร้อมกับ Local Ethereum Network ทำให้เราสามารถทดสอบกับ Blockchain จริง ๆ ได้เลย

ข้อดีหลัก ๆ ของ Hardhat มีดังนี้:

  • Debug ได้ง่าย Error Message ชัดเจน
  • Fork Network ของจริงมาทดสอบได้ ทำให้ Test ได้แบบสมจริง
  • ดู Stack Trace ได้
  • สำหรับเหล่า JS Dev ทั้งหลาย; มี console.log()ด้วย! 😳
  • เป็น JS Ecosystem ทำให้หลายๆคนอาจจะรู้สึกคุ้นเคยเหมือนคนเคยคุย
  • มี Plugins ให้เลือกมาเสริมมากมาย

Outline บทความ

  1. ขึ้น Project Hardhat
  2. เขียน Smart Contract
  3. Automated Test ผ่าน Hardhat
  4. Deploy ด้วย Hardhat Script
  5. Verify Code บน Blockchain Explorer (ด้วย Hardhat Script)

1. ขึ้นของแบบไม่ของขึ้น

ไม่ของขึ้นเพราะง่ายมาก?
เปล่า เพราะงง! ผ่าม!

มาลุยขึ้นโปรเจ็กต์กันครับ!

เริ่มด้วยการขึ้น npm มาก่อน

npm init

จากนั้นลง hardhat

npm install --save-dev hardhat

แล้วสร้างโปรเจ็กต์ด้วย Hardhat — เลือกเป็น TypeScript ครับ (ผม TypeScript FC ❣️)

npx hardhat
เลือกอันนี้ๆๆ

จากนั้นจะได้โครงสร้าง Project มาประมาณนี้

Project Structure

อธิบายความหมายไฟล์สำคัญๆ

  • hardhat.config.ts Configuration สำหรับ hardhat ทั้งหมด อ่านเพิ่ม
  • contracts/ Directory รวมโค้ด Smart Contract
  • scripts/ Directory รวมไฟล์ Scripts ต่างๆ เช่น Script สำหรับ Deploy
  • test/ Directory รวมไฟล์ Automated Test

แก้คำสั่ง test ใน scripts ของ package.json ให้เป็นตามนี้

"scripts": {
"test": "hardhat test"
}

กำหนด Environment

ก๊อปปี้ไฟล์​ .env.example ออกมา เป็น .env

.env คนจริง

แก้ค่าใน .env ให้เป็นค่าจริง ซึ่งสำหรับบทความนี้จะทำอยู่บน Ethereum Ropsten Testnet นะครับ

โดยแต่ละค่าก็คือ:

  • ETHERSCAN_API_KEY API Key ของ etherscan.io (อันนี้ใช้ใน Step 5: Verify Contract เพื่อ Verify Code ผ่าน Script เท่านั้นครับ ข้ามไปแล้ว Verify แบบ Manual เอาก็ได้เหมือนกัน)
  • ROPSTEN_URL RPC URL ของ Ropsten ซึ่งเจ้า Hardhat generate ตัวอย่างมาให้เป็นของ Alchemy แต่ใช้ของ public ใน rpc.info ก็ได้ (เช่น ตอนที่ผมดูจะเป็น https://ropsten.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161)
  • PRIVATE_KEY Private Key ของ Wallet ที่จะใช้ Deploy Contract (กดดูใน Metamask ได้ — 🚨อย่าทำ Key หลุดนะครับบบ🚨)

บทความนี้เขียนตอน Ropsten ยังมีชีวิตอยู่ แต่มันกำลังจะ Deprecated ในอนาคตอันใกล้นี้นะครับ หากตอนอ่านบทความนี้มันสิ้นชีพไปแล้ว ลอง Testnet อื่นดูครับ
อ่านเพิ่มเติม

🕵🏻‍♂️ Checkpoint 1: Project Structure

ถึงจังหวะนี้จะได้ Directory พร้อมโค้ดตัวอย่างมาแล้วครับ ลองทดสอบดูได้โดยการ

npm run test
ผลลัพธ์เวลารันเทสกับ Project ตัวอย่าง

2. เขียน Contract กัน!

โจทย์เป็น Contract ที่จะทำหน้าที่ง่าย ๆ คือ ให้เราสามารถส่งจดหมายรัก (พร้อมเงินเปย์) ให้คนปลายทางสามารถรับไปได้

โดยในจดหมายจะมีข้อความเป็น String ติดไปด้วย — ซึ่งสำหรับ Scope งานนี้ จะเป็นใครก็อ่านได้ไปก่อน ยังไม่ต้องทำระบบกันไม่ให้คนอื่นอ่าน

แชร์ความสวยงามของความรักให้แก่ชาวโลกกันเถอะ 🌝

ย้ำว่าไม่ลง Technical Details นะครับ และโค้ดนี้ยังไม่ได้ผ่านการ Optimize Gas ใดๆ เน้นความอ่านง่ายเป็นหลัก

วางตำแหน่งกล่องจดหมาย

สร้างไฟล์ LoveLetter.sol ไว้ใน Directory contracts/

LoveLetter.sol ใน contracts

⚠️ Challenge1: ก่อนอ่านโค้ดด้านล่าง ดูตัวอย่างจาก contracts/Greeter.sol อ่านโจทย์ แล้วเขียนเองดูครับ

ขึ้นโครงกล่องจดหมาย

ขึ้นโค้ด Contract มาพร้อมกับประกาศ​ Data Structure ที่ต้องใช้ให้ครบ

  • totalLetters บอกว่าตอนนี้มีจดหมายอยู่กี่อัน
  • mapping(uint256 => address) public senders เก็บว่า Letter ID นี้ ใครเป็นคนส่ง
  • mapping(uint256 => address) public receiversเก็บว่า Letter ID นี้ ใครเป็นคนรับ
  • mapping(uint256 => Letter) public letters เก็บข้อมูลภายใน Letter ซึ่งประกอบไปด้วย: ข้อความ, ปริมาณเงินเปย์, สถานะว่าถูกเปิดหรือยัง
  • struct Letter คือ Data Structure ของจดหมาย
  • constructor คือฟังก์ชั่นที่จะทำงานตอนเรา Deploy Contract ครั้งแรก อันนี้มีหน้าที่เพียงเซ็ตค่า totalLetters ให้เริ่มต้นที่ 0 เท่านั้น
  • event คืออีเว้นต์ที่เราสามารถดักจับออกมาดูได้ ถ้า Transaction นั้นผ่าน ซึ่งจะมี 2 อีเว้นต์ที่เราต้องการ คือเมื่อมีคนส่งจดหมาย (Send ) กับเปิดจดหมาย (Open)

Solidity มี Default Value อยู่แล้ว ซึ่งสำหรับ uint256 ก็เป็น 0 นั่นแหละ ดังนั้นจริงๆไม่ต้องเซ็ตก็ได้ แต่เขียนไว้เพื่อความชัดเจน

โครง Contract จดหมายรัก

ช่องส่งจดหมาย

พอมีกล่องจดหมายเปล่า ๆ มาแล้ว ขั้นต่อไปก็คือฟังก์ชั่นสำหรับส่งจดหมาย

ส่งจดหมายรักก

ช่องรับจดหมาย

มีคนส่งก็ต้องมีคนรับ ต่อไปคือฟังก์ชั่นรับจดหมาย ที่เรียกได้เฉพาะคนเป็น Receiver ของจดหมายนั้นๆ

เปิดจดหมายรักกก

ช่องแอบส่อง

ส่งได้รับได้ แต่เรายังไม่มีช่องเอาไว้ส่องของข้างในเลย ดังนั้นมาทำฟังก์ชั่นดูค่าต่าง ๆ ในจดหมายกันต่อ

ส่องจดหมายรักกกก

🕵🏻‍♂️ Checkpoint 2: LoveLetter.sol

ถึงจังหวะนี้ จะได้ Contract LoveLetter มาแล้วครับ อยู่ใน Directory contracts

โดยโค้ดทั้งหมดของ LoveLetter เป็นดังนี้

Contract แห่งความรักกกก

เช็คความถูกต้องได้โดยรันคำสั่ง

npx hardhat compile

ซึ่งผลลัพธ์ควรจะต้องออกมาประมาณนี้ครับ ไม่มี Errors

ขั้นถัดไป มาใช้ความสามารถสุดเทพอย่างหนึ่งของ Hardhat กัน! นั่นก็คือ Automated Test

3. เทสแรกไม่เป็นไร เทสถัดไป.. แตก!

เขียนโค้ดเสร็จแล้ว มานั่งเทสมือมันเหนื่อย เรามองลองเขียน Automated Test ครอบกันดีกว่า

สร้างไฟล์ Test

สร้างไฟล์ loveletter.ts ไว้ใน Directory test/

loveletter.ts ใน test/

เทสอะไรบ้างดี

ลองเขียนเทสกันเบื้องต้นใน Usecase สำคัญ ๆ

  • ทดสอบว่า Deploy สำเร็จ และ Initiate ค่าได้ถูกต้อง
  • ทดสอบว่าสามารถส่งจดหมาย ผ่านฟังก์ชั่น send ได้สำเร็จ และ มีค่าในจดหมายที่ถูกต้อง
  • ทดสอบว่าจดหมายต้องถูกเปิดโดยคนที่ไม่ใช่ผู้รับไม่ได้
  • ทดสอบว่าจดหมายถูกเปิดโดยฟังก์ชั่น openได้โดยคนที่เป็นผู้รับ และได้เงินเปย์ปริมาณที่ถูกต้อง

⚠️ Challenge2: ก่อนอ่านโค้ดด้านล่าง อยากให้ลองเขียนเทสดูเองก่อน โดยดูตัวอย่างจากไฟล์​ test/index.ts ครับ

ทดสอบ หนึ่ง สอง สาม สี่

ฮัลโหล เทส เทส

รันคำสั่งเทสได้โดย

npm run test

ซึ่งผลลัพธ์ก็จะออกมาหน้าตาประมาณนี้ครับ

ผลลัพธ์เทส

[send] 0 1000000000000000000 คืออะไร๊!?

สิ่งนั้นก็คือออออ…

console.log() ภายใน Solidity ครับ!

log ตัวนี้ อยู่ภายในในฟังก์ชั่น send ครับ ลองเล่น log นู่นนี่นั่นออกมาดูได้ อ่าน docs กดที่นี่

🕵🏻‍♂️ Checkpoint 3: ทดสอบความรัก

จังหวะนี้ก็จะได้ไฟล์​ Automated Test เพื่อเสริมสร้างความมั่นใจก่อนเอา Contract ขึ้น Blockchain จริง ๆ แล้วนะครับ

⚠️ Challenge3: อยากให้ลองเพิ่ม Test Case เองครับ โดยเป็นสองเคสต่อไปนี้:

ทดสอบว่า หากเปิดจดหมายเดิมซ้ำ Transaction ควรจะถูก Revert

ทดสอบว่า ส่งจดหมายถัดไป จะเป็นจดหมาย ID 1 และเปิดได้โดยผู้รับเหมือนปกติ

เทสของโจทย์สองข้อนี้ที่ผมเขียน จะอยู่ใน GitHub Repository ที่ผมแปะไว้ท้ายบทความนะครับ — แอบดูตีมือ ☝️

4. -3–2–1 Blast off! Deploy!

โค้ดก็เสร็จแล้ว เทสก็ทำแล้ว คงไม่เหลืออะไรให้ทำอีกนอกจากเอาโค้ดขึ้นละ

ก่อนจะไปท่า Hardhat ขอกลับไปใช้ Remix กันก่อนนะครับ ถือเป็นการ Deploy แบบ Manual โดยไม่ต้องเขียน Script อะไรกันก่อน แบบว่าลำบากก่อนสบายทีหลัง 🌝

Remix สาย(ฉบับ)ย่อ

เริ่มจากสร้างไฟล์ใหม่ใน Remix ตั้งชื่อว่า LoveLetter.sol แล้วก๊อปโค้ดจากไฟล์ contracts/LoveLetter.sol ใน Project เราไปวางครับ

แปะโค้ดออกมาหน้าตาประมาณนี้

จากนั้นเลือกไปที่หัวข้อ Compile แล้ว Config ตามนี้ครับ

  • Compiler เลือกเป็นอะไรก็ได้ใน 0.8.* (ง่าย ๆ ก็เอาตัวที่ใหม่สุดไป) เพราะจะเห็นว่าในโค้ดเราเขียนไว้ว่าเป็น pragma solidity ^0.8.0
  • ติ๊ก Auto compile เพื่อชีวิตที่ดีกว่า (มันจะ Compile ทุกครั้งที่เราแก้โค้ด ทำให้เห็นได้เลยว่ามี Compilation Error อะไรหรือเปล่า)
  • เลือก Contract เป็น LoveLetter.sol
  • กด Compile!
Config ของ Compiler

สุดท้าย ก็คือไปที่หัวข้อ Deploy ครับ ซึ่ง Config ตามนี้

จะข้ามวิธีใช้ Metamask ไปนะครับ

  • เลือก Environment เป็น Injected Web3 ซึ่งจะทำให้มัน Deploy ไปยัง Network ที่ Metamask นั้นเลือกอยู่ ดังนั้นให้ชัวร์ว่า Metamask เลือกเป็น Ropsten อยู่
  • Account จะเป็นกระเป๋าที่ใช้ Deploy ให้ชัวร์ว่ามี Gas อยู่ใน Ropsten — ถ้าไม่มีไปกดจาก Faucet ได้ (ตอนผมกดต้องรออยู่สิบกว่านาทีถึงจะเข้า)
  • เลือก Contract ให้เป็น LoveLetter.sol
Config ของ Deployer

จากนั้น กด Deploy เลย!

หลังจาก Confirm ใน Metamask เสร็จก็นั่งจิบชารอ Transaction เสร็จซักแป๊ปครับ

ซึ่งหากเรียบร้อยจะขึ้นมาอยู่ใน Deployed Contracts แบบนี้

Blasted off!

ถ้าลองก๊อปปี้ Address ไปดูใน ropsten.etherscan.io ได้ จะเห็น Transaction ที่ Deploy อยู่ครับ เยยยย้

⚠️ Challenge4: ลองเล่นใน Remix ทั้ง send ,open และฟังก์ชั่นอื่น ๆ ดูครับ แบบให้ receiver เป็นกระเป๋าอื่น แล้ว open ให้ได้

Deploy ยุ่งยาก ไม่ทำและ เลิกอ่านละบทความ

หยุดก่อนเถิดท่านผู้ใจร้อนรุ่ม

ถัดไปมาลองทำท่าที่ง่ายกว่ากัน!

Deploy ด้วย Script Hardhat

สร้างไฟล์ Script ไว้ที่ scripts/deploy-loveletter.ts ได้เลยครับ (ก๊อปปี้มาจาก scripts/deploy.ts เลยก็ได้)

⚠️ Challenge5: ลองเอา scripts/deploy.ts มาแก้เองดูครับ (แก้น้อยมากกก)

Script สำหรับ Deploy

จากนั้นรัน Command ด้านล่างนี้เพื่อ Deploy ครับ

npx hardhat run scripts/deploy-loveletter.ts --network ropsten
ผลลัพธ์หลังจากรัน Command

🕵🏻‍♂️ Checkpoint 4: ปลดปล่อยความรักสู่โลก

ถึงจุดนี้ก็จะได้ Smart Contract ขึ้นไปอยู่บน Blockchain แล้วนะครับ

ลองก๊อปปี้ Address จากผลลัพธ์ใน Terminal ไปดูที่ https://ropsten.etherscan.io/ ได้เลย

Contract เราใน ropsten.etherscan.io !

ใครลองทำตามแล้วเทียบผลลัพธ์ใน Etherscan ดู อาจจะพบว่า ตรง Contract ของคุณไม่มีติ๊กสีเขียวอยู่

ซึ่งสิ่งนั้นจะเกิดขึ้นได้ เราต้อง “Verify” โค้ด Smart Contract เราก่อน เพราะปกติแล้วเราจะอ่านโค้ดของ Contract ที่ยังไม่ Verify ไม่ได้ จะเห็นเป็น Bytecode ยาว ๆ

Verify ยังไง? ตามมาดูกัน! หัวข้อสุดท้ายแล้ววว

5: ยืนยันว่ารักยังยั่งยืนอยู่ยั้งยืนยง

เนื่องจาก Blockchain นั้น ใคร ๆ ก็สามารถ Deploy Contract ขึ้นไปได้ (Permissionless) และใครก็สามารถดูโค้ดได้ด้วย

…แต่โค้ดที่เห็นนั้นก็จะเป็น ByteCode ที่มนุษย์เรา ๆ อ่านไม่รู้เรื่อง

Bytecode ของ Contract ที่ยังไม่ Verify

ดังนั้น ถ้านักพัฒนาอยากให้ผู้ใช้สามารถมาตรวจสอบโค้ด(แบบที่มนุษย์อ่านออก)ด้วยตัวเอง ก็ต้องยืนยัน(Verify)โค้ดกันเสียก่อน ว่าโค้ดต้นฉบับก่อนที่จะกลายเป็น ByteCode นั้น หน้าตาเป็นยังไง

ซึ่งด้วยพลังแห่ง Hardhat นั้น ก็ทำได้ง่ายมาก โดยรันแค่นี้เลย! (ต้อง Config .env ตัว ETHERSCAN_API_KEY ก่อนนะครับ)

npx hardhat verify --network ropsten 0x8b53e7A4aBa76b8e2cdA11b0A3d7cBf5847C5aB6

ทีนี้ไม่ว่าใคร ก็สามารถดูโค้ดของเรา และตรวจสอบด้วยตัวเองได้แล้วครับ!

อ่านโค้ดได้เลย!

⚠️ Challenge6: ลอง Verify ในเว็บ Etherscan ดู ได้เหมือนกันครับ

ซึ่งถ้า Deploy โค้ดเดิมเป๊ะไป มันจะมองว่าเราเคย Verify ไปแล้ว ทำให้เราลอง Verify ใหม่ไม่ได้ เลยขอ Challenge ให้แก้โค้ด โดยการเอา console.log ออกครับ เพราะอย่างไร console.logใช้ได้เฉพาะในตอนเราเทสด้วย Hardhat เท่านั้นอยู่แล้ว ขึ้น Blockchain ไปไม่มีประโยชน์อะไร รังแต่ทำให้ Contract เราใหญ่ขึ้น

🕵🏻‍♂️ Checkpoint 5: ความรักจะยั่งยืนตลอดไป

ถึงจุดนี้ เราก็จะได้ Smart Contract ที่ผ่านการทดสอบอย่างหนักหน่วง(?) ขึ้นไปอยู่บน Blockchain จริง ๆ แล้วนะครับ

แถมการ Deploy & Verify ก็ยังง่ายดาย เพียงแค่รัน Command บรรทัดเดียวเท่านั้นเอง!

⚠️ Challenge7: Deploy & Verify บน Mainnet ดูครับ (ถ้า Ethereum แก๊สอาจจะแพงกระเป๋าตังขาดกระจุย ลองดู​ Chain อื่น ๆ เช่น Binance Smart Chain, Polygon ดูได้ครับ)

หากอยากลองยิง Smart Contract ส่งจดหมายรักบน Blockchain จริงดู ขอแนะนำให้รู้จักกับบทความสุดเทพ by Earth ชาว Cleverse อีกคนนึงครับ จะได้ลองใช้ JavaScript ในการ Interact กับ Smart Contract ผ่าน Ethers.js ซึ่งง่ายมากกก

🏁 Goal! ถึงจุดหมายแล้ว

ยินดีกับทุกท่านด้วย ที่มาถึงจุดนี้ได้ครับ ขอปรบมือให้สองที 👏

แปะ แปะ

ขอ Recap อีกสักทีว่าเราได้ทำอะไรไปบ้างในบทความนี้นะครับ

  1. ขึ้น Project Hardhat
  2. เขียน Smart Contract
  3. Automated Test ผ่าน Hardhat
  4. Deploy ด้วย Hardhat Script
  5. Verify Code บน Blockchain Explorer (ด้วย Hardhat Script)

ใช่ครับ ผมก๊อปข้างบนมา 🌝

ทั้งนี้ทั้งนั้น หนทางสู่การเป็นเซียน Solidity นั้นยังอีกยาวไกล เรายังเพิ่งเริ่มก้าวแรกไปด้วยกันเท่านั้น ยังมีเส้นทางสูงชันกว่าจะไปถึงยอดเขาครับ

ดังนั้นติดตามรอดูบทความ Advanced ยิ่งขึ้นจาก Cleverse ได้เลยครับ (เขียนเมื่อไหร่อีกเรื่อง 🎅🏼)

Resources

อันนี้ใช้เริ่มต้นได้ดี

--

--

Aikdanai Sidhikosol
Cleverse, a Web3 Focused Venture Builder

An average guy who’s interested in not-so-average stuff 👨‍💻 Software Engineer @Cleverse