ลองใช้ Foundry เขียน Smart Contract
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 ประมาณนี้
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 อ่านเพิ่มเติมได้ที่นี่
ลองเขียน 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 ตามรูปด้านล่าง
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 ตามรูปด้านล่าง
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 ตามรูป
ลอง transfer token ด้วยคำสั่ง
cast send 0x5fbdb2315678afecb367f032d93f642f64180aa3 "transfer(address,uint256)" 0x22fc57A3A0F587343300cf70198af07AD9f4Fe3F 10000000000000000 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
“transfer(address,uint256)” 0x22fc… 10000000000000000 คือ ระบุ function signature และ input ตาม parameter type
จะได้ result ตามรูป
ลองเช็ค balance ของ address 0xf39f… ด้วยคำสั่ง
cast call 0x5fbdb2315678afecb367f032d93f642f64180aa3 "balanceOf(address)(int)" "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
ได้ result ตามรูป
จะเห็นว่า Token ถูก transfer ไปยัง address 0x22fc… แล้ว
Conclusion
Foundry ถือเป็นอีกหนึ่ง toolchain ที่น่าสนใจมากๆ ซึ่งนอกจากจุดเด่นด้านความเร็วในการ compile และ test แล้วนั้น การ test ของ foundry เองยังมีตัวช่วยอย่าง Cheatcodeในการ mock data ต่างๆ รวมถึงสามารถแก้ไข state ของ EVM ได้ด้วย เช่น block number หรือ block timestamp ที่ทำได้ง่ายเพียงแค่ code บรรทัดเดียว และไม่ต้องลง dependency เพิ่มเติม