Smart contract mutation testing with Gambit and Vertigo
note from The #1 Most Underrated Web3 Security Tool by Owen Thurm
Mutation testing คืออะไร
คือการที่เราเอา code original มาแก้ ทำให้เกิด code version bug ขึ้นมา
ถ้า file test เอามารัน code original และ code bug แล้วดัน test ผ่านทั้งคู่
แสดงว่า file test ไม่ได้ test ในบรรทัดนั้นๆ
ดังนั้น mutation testing = การใส่ bug ไปใน code เพื่อวัดคุณภาพ file test
ref [https://www.rareskills.io/post/solidity-mutation-testing]
การรัน Mutation testing ด้วย Gambit
Gambit = tool สำหรับทำ mutation testing บน smart contract
ตัวอย่าง
จาก contract Ticketer ทำได้ 2 อย่างคือ buy ticket กับ owner collect ค่า ticket
โดยเราเขียน file test ของ Ticketer contract ไว้
เมื่อเรารัน Gambit จะได้ contract version ที่แก้ code (แก้ code ควรเท่ากับใส่ bug ถ้า code version เดิมไม่มี bug)
โดยในแต่ละ version จะมี comment บอกเอาไว้ว่า gambit ไปแก้บรรทัดไหน
จากรูปจะเห็นว่า gambit แก้ให้เกิด revert Invalid value() เสมอ ไม่ว่าจะใส่ input มาเป็นอะไร
โดยเราจะเอา mutated contract version ต่างๆมารันกับ file test
จะพบว่ามี mutated contract ที่ file test ไม่ครอบคลุม
เช่น version 2
โดยใน version 2 เราจะเห็นว่า Gambit ทำให้บรรทัด 18 กลายเป็น dead code = เอาบรรทัดนี้ออกจากโปรแกรม
แต่ file test ของเราดันรันผ่านทั้ง version original และ version 2 (version bug เพราะ gambit แก้ code)
เพราะว่า file test ของเราไม่ได้ เช็คว่าควร revert Invalid value เมื่อไหร่นั่นเอง
โอเค งั้นมาแก้ file test เราให้ expect การ revert invalid value เมื่อ amount == 0 กัน
ตอนนี้ file test เราจะรัน test กับ original code ผ่าน และรัน test กับ bug code version 2 ไม่ผ่าน
แสดงว่า file test เรา ครอบคลุมการ expect revert invalid value แล้วนั่นเอง
โดยนอกจากการ unit test เพื่อลดจำนวน undetected mutated bug ลงแล้ว
เราก็สามารถเขียน fuzz test ลงไปใน file test เลยได้ด้วย ถ้าใช้ foundry
(การทำ fuzz test จะช่วยลดแรงเราในการคิด input มาป้อน function ใน unit test)
แต่การ fuzz เพื่อ random function input ก็ไม่ได้ทำให้ตัว test เจาะไปยังทุก branch ของ code ได้เสมอไป
เช่น จาก mutated code
เป็นการเอา บรรทัด การ detect failed transfer eth ออกจาก code
ซึ่ง file test ดันรันผ่าน ทั้ง original code และ code ที่ เอา บรรทัด การ detect failed transfer eth ออกจาก code
ดังนั้น file test ไม่ได้ expect การ detect failed transfer eth นั่นเอง
ซึ่ง fuzz test random ให้ test case อันนี้ออกมาไม่ได้ละนะ ต้องเขียน unit test เอง
การรัน Mutation testing ด้วย Vertigo
Vertigo คือ tool สำหรับทำ mutation testing บน smart contract เหมือนกับ Gambit
ความแตกต่างระหว่าง Vertigo และ Gambit
1. Vertigo ไม่ต้องเขียน script test original vs mutated เอง
มาดู flow การใช้ Gambit กันก่อน
รัน Gambit => ได้ mutated (bug) contract ออกมา
เขียน script => เพื่อ
1. นำ file test ไปรันบน original code และ mutated (bug) contract
2. เทียบผลลัพธ์ ถ้า file test รันผ่านทั้ง contract ที่ แก้ และ ไม่แก้ แสดงว่า file test ไม่ได้ expect ในบรรทัดที่แก้ไป
อันนี้เป็น flow Gambit
แต่ Vertigo จะลดขั้นตอนลง เหลือแค่ รัน Vertigo โดย Vertigo จะทำให้ทั้งหมด โดยไม่ต้องเขียน script เอง
2. Vertigo generate mutated (bug) contract ได้น้อยกว่า Gambit
ตัวอย่าง
จะเห็นได้ว่า Vertigo สร้าง mutated contract ได้น้อยกว่า gambit แต่ Vertigo จะรัน full automate จนออกผลลัพธ์ ได้เลย ไม่ต้องเขียน script มาช่วย