ลองใช้ Foundry เขียน Smart Contract

Thirak Chuayjan
5 min readAug 1, 2022

--

src: https://book.getfoundry.sh/images/foundry-banner.png

Foundry manages your dependencies, compiles your project, runs tests, deploys, and lets you interact with the chain from the command-line and via Solidity scripts.

Overview

Foundry เป็น development toolchain(เครื่องมือที่ใช้สำหรับพัฒนา) ของ smart contract ซึ่งคล้ายกับ Truffle และ Hardhat โดยมีข้อเด่นหลักๆ คือ
1. สามารถเขียน test และ script ด้วยภาษา solidity ได้เลยโดยไม่ใช้ javascript นั่นหมายความว่าไม่จำเป็นต้องมี node module หรือจัดการกับตัวเลขด้วย Big Number
2. เนื่องจาก Foundry ถูกพัฒนาด้วยภาษา Rust ทำให้การ compile และการ test มีความเร็วสูงมาก (สามารถดูผล benchmark ได้ที่นี่)

Foundry ได้แบ่ง command-line tool ออกเป็น 3 ส่วนคือ
1. forge สำหรับ develop, test และ deploy smart contracts
2. cast สำหรับ call smart contract, send transaction หรือ read data
3. anvil สำหรับ run local ethereum node (เหมือนกับ Ganache หรือ Hardhat node)

Installation

เปิด terminal ขึ้นมาจากนั้น download Foundry กันก่อน

curl -L https://foundry.paradigm.xyz | bash

เสร็จแล้วให้ใช้คำสั่งเพื่อติดตั้ง foundry ตามด้านล่าง

foundryup

Let’s Begin

เริ่มสร้าง Foundry project ด้วยคำสั่ง

forge init hello_foundry

เมื่อเปิดดู directory ชื่อ hello_foundry จะได้ structure ประมาณนี้

Foundry project structure

lib ใช้เก็บ dependency ของ project
script
ใช้เก็บ script สำหรับ deploy
src ใช้เก็บ smart contract
test ใช้เก็บ test case ต่างๆ
foundry.toml คือ config file ของ foundry project (อ่านเพิ่มเติมได้ที่นี่)

เราลองมาเขียน smart contract ง่ายๆโดยใช้ ERC20 จาก Openzeppelin ซึ่งจะต้อง install dependency ก่อน เพื่อใช้ในการ import เข้ามาใน smart contract ของเรา

forge install Openzeppelin/openzeppelin-contracts

เปลี่ยนชื่อ file ใน src เป็น SampleToken.sol และแก้ไข code โดยใส่การ mint token เข้าไปด้วย

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

forge build

เมื่อ compile สำเร็จเราจะได้ directory ชื่อ out และ cache เพิ่มขึ้นมา

ABI ของ smart contract ที่ compile จะถูกเก็บอยู่ใน out directory

Test

หลังจากเขียน smart contract แล้วเรามาลองเขียน test ซึ่งเริ่มจากการเช็ค totalSupply และ balanceOf ของ minter กันก่อน โดยให้แก้ไขชื่อ file ใน test เป็น SampleToken.t.sol แล้วเพิ่ม test function เข้าไป

Cheatcode

สำหรับการ test ของ foundry นั้นเราสามารถจัดการกับ state ของ blockchain ได้ง่ายๆด้วยสิ่งที่เรียกว่า cheatcode จากตัวอย่างด้านบนเรากำหนด msg.sender เป็น bob ได้ด้วย vm.prank(bob) (Cheatcode อ่านเพิ่มเติมได้ที่นี่)

Assertion

การ assert ค่าของ foundry ทำได้โดยการใช้ assertEq() จากตัวอย่างคือการ assert ค่า totalSupply ว่าเท่ากับ 10 ether(10*10¹⁸) หรือไม่ (Assertion อ่านเพิ่มเติมได้ที่นี่)

ใช้คำสั่งตามด้านล่างเพื่อ test SampleToken.t.sol

forge test

หากต้องการ print log ทำได้โดยการใส่ console.log() แล้วตอน test ให้เพิ่ม option ตามด้านล่างเพื่อ print log บน terminal

forge test -vv

-vv flag อ่านเพิ่มเติมได้ที่นี่

example when used console.log()

ลองเขียน test event ด้วย vm.expectEmit() โดยแก้ไข code เพิ่มเติม

จาก code เราทำการ check topic1, topic2 และ data รวมถึง contract address ที่ function transfer นั้น emit event ออกมาว่าถูกต้องหรือไม่

เรื่อง topic ของ event log อ่านเพิ่มเติมได้ที่นี่

ลองเขียน test revert ด้วย vm.expectRevert()โดยเพิ่ม code ตามนี้

ลองเขียน fuzz test ตาม code ด้านล่าง

Fuzz Testing

คือการสุ่ม input เข้าไปใน function ตาม type ของ variable (ตาม code ด้านบนคือตัวแปร amount ที่มี type เป็น uint256)
vm.assume() ใช้กำหนดค่า variable ตามตัวอย่างคือไม่เกิน 10 ether

เรื่อง Foundry fuzz testing อ่านเพิ่มเติมได้ที่นี่

Gas Tracking

อีกหนึ่ง feature ของ Foundry คือสามารถออก gas report ได้ว่าแต่ละ function ของ smart contract ที่เรียกจะใช้ค่า gas ประมาณเท่าไหร่

forge test --gas-report

result ที่ได้บน terminal

นอกจากนี้ยังมีคำสั่ง snapshot ที่ใช้บอกว่าการ test ในแต่ละ functionใช้ค่า gas ไปเท่าไหร่

forge snapshot

result ที่ได้จะอยู่ใน file ชื่อว่า .gas-snapshot

Gas Tracking อ่านเพิ่มเติมได้ที่นี่

Deploy Smart Contract

หลังจากเรา compile และ test smart contract เรียบร้อยแล้วก็ถึงเวลา deployโดยเราจะทำการ deploy บน local node ของ foundry กัน เริ่มจากเปลี่ยนชื่อ file ใน script เป็น SampleToken.s.sol และแก้ไข code ตามด้านล่าง

ตอนนี้เรามี script ที่พร้อมจะ deploy แล้ว ต่อไปเราจะ start environment สำหรับ deploy smart contract กัน

Foundry Anvil

เป็น command-line ที่ใช้สำหรับ run local node โดยใช้คำสั่งตามด้านล่าง

anvil

เราได้ environment ใน terminal ตามรูปด้านล่าง

run anvil with 10 wallets by default

Deploy smart contract โดยใช้คำสั่ง

forge script script/SampleToken.s.sol:SampleTokenScript --fork-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast

script script/SampleToken.s.sol:SampleTokenScript คือระบุ location และ name ของ script ที่ต้องการ
— fork-url http://localhost:8545 คือ endpoint ของ environment ที่จะ deploy
— private-key 0xac09… คือ key ของ msg.sender ในที่นี้ใช้ key ที่ anvil generate ไว้ให้
— broadcast คือ ยิง transaction ไปยัง endpoint ที่ต้องการ (ถ้าไม่ใส่ option นี้ foundry จะแค่สร้าง transaction ไว้ใน broadcast directory)

หากสำเร็จเราจะได้ log ตามรูปด้านล่าง

result after deploy sample token

Foundry Cast

หลังจาก deploy smart contract แล้วเราจะมาลองเรียก SampleToken โดยใช้ cast command-line ของ foundry กัน ซึ่งเราจะลอง get token name กันตามคำสั่ง

cast call 0x5fbdb2315678afecb367f032d93f642f64180aa3 "name()(string)"

0x5fbdb… คือ address ของ contract ที่ deploy
“name()(string)” คือ function signature ของ contract โดย (string) ด้านหลังคือการบอกให้ convert return result ที่ได้เป็น string (ถ้าไม่ระบุจะได้เป็น Hex)

เราจะได้ result ตามรูป

result after call “name()”

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

cast send 0x5fbdb2315678afecb367f032d93f642f64180aa3 "transfer(address,uint256)" 0x22fc57A3A0F587343300cf70198af07AD9f4Fe3F 10000000000000000 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

“transfer(address,uint256)” 0x22fc… 10000000000000000 คือ ระบุ function signature และ input ตาม parameter type

จะได้ result ตามรูป

result after send “transfer()”

ลองเช็ค balance ของ address 0xf39f… ด้วยคำสั่ง

cast call 0x5fbdb2315678afecb367f032d93f642f64180aa3 "balanceOf(address)(int)" "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"

ได้ result ตามรูป

result after call “balanceOf()”

จะเห็นว่า Token ถูก transfer ไปยัง address 0x22fc… แล้ว

Conclusion

Foundry ถือเป็นอีกหนึ่ง toolchain ที่น่าสนใจมากๆ ซึ่งนอกจากจุดเด่นด้านความเร็วในการ compile และ test แล้วนั้น การ test ของ foundry เองยังมีตัวช่วยอย่าง Cheatcodeในการ mock data ต่างๆ รวมถึงสามารถแก้ไข state ของ EVM ได้ด้วย เช่น block number หรือ block timestamp ที่ทำได้ง่ายเพียงแค่ code บรรทัดเดียว และไม่ต้องลง dependency เพิ่มเติม

Source Code

Reference

--

--

Thirak Chuayjan

Blockchain Warrior, technology is like art a mixture of creativity and intelligence.