DeFi Risks 101 — 1: An Insecure Fork of MasterChef

สวัสดีผู้อ่านทุกท่านและขอต้อนรับสู่ซีรีส์บทความล่าสุดจาก Inspex กับ DeFi Risks 101 ครับ ในซีรีส์ DeFi Risks 101 นี้ เราจะมาพูดถึงความเสี่ยงในหลายประเด็นของแพลตฟอร์ม DeFi ในเชิงเทคนิคจากประสบการณ์การตรวจสอบความปลอดภัยให้กับแพลตฟอร์ม DeFi และจากการวิเคราะห์การโจมตีที่เกิดขึ้นกับแพลตฟอร์มต่าง ๆ เราเชื่อว่าบทความในซีรีส์นี้จะเป็นหนึ่งในตัวช่วยให้ผู้ที่สนใจใน DeFi ใช้ในการทำความเข้าใจและประเมินความเสี่ยงก่อนตัดสินใจใช้งานแพลตฟอร์มได้ครับ

An Insecure Fork

ในการพัฒนาซอฟต์แวร์ คำว่า fork เป็นได้ทั้งคำกริยาซึ่งหมายถึงการแยกการพัฒนาโครงการซอฟต์แวร์ด้วยการคัดลอกซอร์สโค้ดทั้งหมด แล้วนำมาพัฒนาโดยเป็นอิสระจากโครงการต้นฉบับ คำว่า fork ยังเป็นคำนามซึ่งหมายถึงโครงการซอฟต์แวร์ที่ถูกคัดลอกจากการ fork ได้อีกด้วย

หากใครเคยมีประสบการณ์กับแพลตฟอร์ม DeFi มาประมาณหนึ่ง เราอาจจะพอคุ้นกับประโยคว่า “แพลตฟอร์มนี้ fork มาจากโครงการ A” และถึงแม้บางท่านอาจจะไม่เคยได้ยินประโยคนี้มาก่อนเลย ผมเชื่อว่ามีไม่น้อยที่เราจะรู้สึกว่าบางแพลตฟอร์มนั้นมีลักษณะหรือวิธีการใช้งานที่แทบจะเหมือนกัน แตกต่างกันแค่ในรายละเอียด

การ fork แพลตฟอร์ม DeFi นั้นเป็นเรื่องทั่วไปที่เกิดขึ้นอยู่ตลอด แพลตฟอร์มหลายแพลตฟอร์มซึ่งเกิดขึ้นมาใหม่ถูกนำมาพัฒนาและให้บริการต่อผ่านการ fork จากแพลตฟอร์มที่มีอยู่ก่อนแล้ว การ fork เกิดขึ้นกับแพลตฟอร์ม DeFi ได้ทั้งแพลตฟอร์ม หรืออาจทำกับบางส่วนของแพลตฟอร์ม เช่น ส่วนของหน้าตา (frontend) หรือส่วนของ contract ก็ได้

เพราะการ fork จะทำให้เราได้สำเนาที่เหมือนกับต้นฉบับ หากต้นฉบับมีปัญหาในจุดใด สำเนาของเราย่อมมีปัญหาในจุดนั้นตาม และด้วยความอิสระระหว่างสำเนากับต้นฉบับ หากต้นฉบับรับรู้ถึงปัญหาและมีการแก้ไข การแก้ไขที่เกิดขึ้นกับต้นฉบับก็ย่อมไม่เกิดขึ้นกับสำเนาและทำให้สำเนายังคงมีปัญหาต่อไปหากผู้ที่ fork ไปไม่ทำการแก้ไขเอง

หากเราเป็นนักพัฒนา บทความนี้อาจช่วยเป็นจุดเริ่มต้นให้เรากลับไปทำความเข้าใจปัญหาที่มีอยู่เดิมในแพลตฟอร์มต้นฉบับ ซึ่งจะนำไปสู่การจัดการปัญหาที่เราอาจไม่เคยรู้มาก่อนได้ และอาจช่วยเราจากการเข้าไปมีส่วนร่วมกับปัญหาผ่านการพัฒนาต่อโดยไม่ได้ตั้งใจได้ด้วย

และหากเราเป็นผู้ใช้งาน การเข้าใจความเสี่ยงอย่างถูกต้องนั้นเป็นสิ่งสำคัญอย่างยิ่งต่อการลงทุน บทความนี้อาจช่วยให้เรารู้ถึงความเสี่ยงของแพลตฟอร์ม และอาจช่วยให้เราสังเกตเห็นหากผู้พัฒนาแพลตฟอร์ม DeFi ตั้งใจที่จะใช้ปัญหาที่ถูกสืบทอดมาเหล่านี้ในการหาประโยชน์เข้าตัวเองได้อีกด้วย

The MasterChef

MasterChef คือชื่อของ contract หนึ่งที่มีที่มาจากแพลตฟอร์ม SushiSwap (ถูกเปลี่ยนชื่อใหม่เป็น Sushi ในเวลาต่อมา)

หลังจากช่วกลางปี 2020 ในยุคที่แพลตฟอร์ม DeFi อย่าง Uniswap ยึดครองตลาดภายใต้แนวคิดของการมี liquidity pool เพื่อกำหนดสภาพคล่องในการแลกเปลี่ยน พร้อมกับให้ค่าธรรมเนียมที่เกิดจากการแลกเปลี่ยนกลับไปยังผู้ที่เข้ามาให้สภาพคล่องหรือ liquidity provider เพื่อเป็นแรงจูงใจในการให้สภาพคล่องต่อ แพลตฟอร์ม SushiSwap ได้เกิดขึ้นมาภายใต้แนวคิดของการให้และใช้เหรียญที่มีชื่อว่า $SUSHI เป็นหนึ่งในกลไกเพื่อตอบแทนและจูงใจ liquidity provider รายละเอียดเพิ่มเติมในส่วนนี้สามารถอ่านได้จากบล็อกของ Sushi

เพื่อให้เกิด $SUSHI ที่แสนอร่อยหนึ่งคำ เราจึงจำเป็นต้องมีสุดยอดเชฟซูชิฝีมือดี หน้าที่นั้นจึงถูกมอบหมายให้กับ MasterChef

MasterChef contract ของ SushiSwap

หน้าที่แต่เดิมของ MasterChef คือการรับฝาก liquidity provider (LP) token รวมไปถึงคำนวณการออก $SUSHI ให้กับ liquidity provider ซึ่งกลายมาเป็นส่วนหลักของแนวคิดการทำ yield farming

เมื่อความนิยมของแพลตฟอร์ม DeFi เริ่มเพิ่มมากขึ้นและตามมาด้วยการเกิดขึ้นใหม่ของแพลตฟอร์ม DeFi อีกเป็นจำนวนมาก MasterChef ก็ถูก fork และถูกพัฒนาเกิดเป็นเวอร์ชันใหม่ ก่อนที่เวอร์ชันใหม่เหล่านั้นจะถูก fork และทำซ้ำต่อไปอีกเรื่อย ๆ

ด้วยหน้าที่ที่สำคัญและอาจจะซับซ้อนในบางกรณีของ MasterChef ที่สืบทอดมาพร้อมกับความเสี่ยงและความเป็นไปได้ที่ความซับซ้อนนั้นจะเกิดเป็นปัญหา เราจึงขอหยิบ MasterChef มาเป็นพระเอกที่มีหลายบทบาทในบทความนี้ พร้อมกับพูดถึงปัญหาและความเสี่ยงที่แฝงและถูกสืบทอดมาจากการ fork MasterChef ครับ

SushiSwap’s Migrator

ตัวอย่างที่เราจะมาพูดถึงกันเป็นตัวอย่างแรกนั้นอยู่ไม่ไกลจากเรื่องราวในส่วนก่อนหน้าเพราะมันยังอยู่ใน MasterChef ของ SushiSwap โค้ดในส่วนนี้มีส่วนสำคัญในปฏิบัติการโอนถ่ายสภาพคล่องระหว่าง Uniswap และ SushiSwap ในอดีตภายใต้ชื่อเรียกเทคนิคว่า Vampire Attack โค้ดในส่วนนี้ประกอบไปด้วยฟังก์ชันทั้งหมด 2 ฟังก์ชัน คือ setMigrator() และ migrate() ซึ่งสามารถดูได้จากไฟล์ MasterChef.sol

ฟังก์ชัน migrate() ซึ่งรับค่า _pid หรือลำดับของ pool ใน MasterChef และถูกกำหนดให้สามารถถูกเรียกใช้งานได้โดยผู้ใช้งานทุกคน เมื่อถูกเรียกใช้จะทำให้เกิดเหตุการณ์ดังต่อไปนี้

  1. ทำการตรวจสอบว่าค่า address ที่อยู่ในตัวแปร migrator ว่าไม่ได้มีค่า address เท่ากับ 0 เงื่อนไขในบรรทัดนี้มีความหมายเท่ากับว่าการเรียกใช้ฟังก์ชัน migrate() จะต้องมีการกำหนดค่าของตัวแปร migrator ผ่านฟังก์ชัน setMigrator() ก่อนสำหรับกรณีนี้
  2. ค่า _pid ถูกนำไปใช้อ้าง pool ที่มีอยู่เพื่อดึง address ของ LP token
  3. มูลค่าของ LP token ถูกดึงมาเก็บไว้ในตัวแปร ค่านี้จะถูกใช้เพื่อเปรียบเทียบมูลค่าที่เปลี่ยนแปลงเพื่อยืนยันการโอนถ่ายว่าสมบูรณ์หรือไม่
  4. ฟังก์ชัน safeApprove() ถูกเรียกใช้เพื่อกำหนดมูลค่าที่อนุญาตให้ address ของ migrator สามารถทำธุรกรรมได้
  5. ฟังก์ชัน migrate() ซึ่งอยู่ภายใต้ Migrator contract ถูกเรียกเพื่อโอนถ่าย LP token ทั้งหมดไปยัง address ที่ถูกระบุไว้ในตัวแปร migrator พร้อมกับตรวจสอบค่าที่เปลี่ยนแปลงเพื่อยืนยันการโอนถ่ายและอัปเดตค่า address

เราสามารถเห็นได้ในกระบวนการทำงานของฟังก์ชัน migrate() ซึ่งปรากฎใน MasterChef ว่ามันมีพลังในการโอน LP token ไปยังบุคคลอื่นได้ ความสำคัญของฟังก์ชัน migrate() เชื่อมโยงไปยังฟังก์ชัน setMigrator() ในทันทีเนื่องจากฟังก์ชัน setMigrator() ทำหน้าที่ในการกำหนด address ที่จะรับ LP token อ้างอิงจากโค้ดของฟังก์ชัน setMigrator() ผู้ที่จะสามารถเรียกใช้ฟังก์ชันนี้เพื่อกำหนด address ของ migrator ได้คือ owner

มาถึงจุดนี้เราพอจะเห็นความเป็นไปได้แล้วใช่ไหมครับว่า 2 ฟังก์ชันนี้สร้างสิ่งที่เรียกว่า rug pull ได้

การมีอยู่ของฟังก์ชันที่เกี่ยวข้องกับการโอนย้ายสภาพคล่องถูกพิสูจน์แล้วว่ามีความเสี่ยงจากการถูกใช้เพื่อทำ rug pull หลายต่อหลายครั้ง ความพยายามในการจัดการกับเหตุการณ์ในลักษณะนี้เพื่อไม่ให้เกิดซ้ำมีทั้งการใช้ฟังก์ชัน Timelock เพื่อเพิ่มโอกาสในการลดกระทบ หรือการป้องกันโดยการนำโค้ดในส่วนนี้ออกไปเลย

และนี่คือส่วนแรกที่เรานำมาพูดถึงสำหรับปัญหาและความเสี่ยงที่ถูกสืบทอดและส่งถ่ายมาใน MasterChef contract ครับ

PancakeSwap’s Syrup Pools

PancakeSwap เป็นอีกหนึ่งแพลตฟอร์ม DeFi ในธีมอาหารที่ไม่ว่าใครก็ต้องเคยได้ยินชื่อ หากเราย้อนดูประวัติการพัฒนาไฟล์ MasterChef.sol ซึ่งเก็บ MasterChef contract เอาไว้ เราจะสามารถเห็นได้จากประวัติการพัฒนาว่าไฟล์ MasterChef.sol ของ PancakeSwap ก็ถูก fork มาจากไฟล์ MasterChef.sol ของ SushiSwap ในขณะนั้น

อย่างไรก็ตามจุดที่เราจะมาพูดถึงกันในส่วนนี้นั้นอยู่ในจุดที่ทีม PancakeSwap มีการพัฒนาขึ้นมาจากเดิมแต่ยังคงอยู่ใน MasterChef contract การพัฒนาเพิ่มขึ้นมาใหม่นี้ถูกรู้จักกันในชื่อของ Syrup Pools ซึ่งมาพร้อมกับ $SYRUP

เราอาจเรียก Syrup Pools ได้ว่าเป็นหนึ่งในความพยายามของทีม PancakeSwap ในการสร้างระบบนิเวศน์ใหม่ให้กับแพลตฟอร์มซึ่งอาจนำไปสู่แนวทางในการใช้ประโยชน์และเพิ่มมูลค่าให้กับเหรียญหรือกระบวนการที่เกี่ยวข้อง

การฝาก $CAKE ใน MasterChef contract จะมีการสร้าง $SYRUP ขึ้นมาให้กับผู้ใช้ โดยเหรียญ $SYRUP นี้สามารถนำมาฝากไว้ใน Syrup Pools เพื่อรับผลตอบแทนเป็นเหรียญในสกุลอื่น ๆ ได้ และหากผู้ใช้ต้องการถอน $CAKE ออกมาจาก MasterChef contract ผู้ใช้จำเป็นจะต้องคืน $SYRUP เป็นจำนวนเท่ากันกับที่ถูกสร้างขึ้นมาในตอนแรก

หลังจากที่ Syrup Pools และ $SYRUP ถูกเปิดตัวได้ไม่นาน ในวันที่ 3 พฤศจิกายน 2020 ทาง PancakeSwap ก็ออกประกาศหยุดการสนับสนุน $SYRUP โดยทันทีหลังจากมีการตรวจพบปัญหาใน contract ซึ่งทำให้เกิดการออก $SYRUP ได้มากเกินความจริง ปัญหานี้เกิดจากแนวคิดในการใช้ฟังก์ชันซึ่งพบเห็นได้ทั่วไปแถมเป็นฟังก์ชันที่มีประโยชน์อีกเสียด้วยมาโจมตี ฟังก์ชันนั้นคือ emergencyWithdraw()

ฟังก์ชัน emergencyWithdraw() เป็นฟังก์ชันที่มีหน้าที่ตามชื่อคือทำให้เราสามารถทำการ “ถอนแบบฉุกเฉิน” ได้ในทันที ลองนึกถึงสถานการณ์ที่แพลตฟอร์ม DeFi ซึ่งเราใช้งานไม่สามารถเข้าถึงได้เพราะถูกโจมตีหรือเกิดปัญหา หากเราต้องการถอนทรัพย์สินที่ไปวางไว้ออก ฟังก์ชัน emergencyWithdraw() ก็สามารถถูกใช้เพื่อภารกิจนี้ได้ครับ

หากเรายังจำกันได้ว่ากลไกของการได้ $SYRUP มานั้นเกิดขึ้นได้จากการนำ $CAKE ไปวางทำให้ฟังก์ชัน syrup.mint() ถูกเรียกใช้งาน และต้องมีไปคืนถ้าจะเอา $CAKE ออก ส่วนที่เราเอาไปคืนก็จะถูกทำลายทิ้งด้วยฟังก์ชัน syrup.burn() ถ้าหากเราไม่ออกจาก Syrup Pools ด้วยการนำ $SYRUP ไปคืน แต่ออกด้วยฟังก์ชัน emergencyWithdraw() ที่ไม่ได้เขียนให้มีการทำลายเหรียญ​ $SYRUP ทิ้งล่ะครับ อะไรจะเกิดขึ้น?

ผลลัพธ์ที่จะเกิดขึ้นคือเราสามารถสร้าง $SYRUP ได้มากเกินความเป็นจริงเพราะฟังก์ชัน emergencyWithdraw() นั้นไม่มีเงื่อนไขของการทำลาย $SYRUP ทิ้งเมื่อนำ $CAKE ออกมาอยู่

การโจมตีนี้สามารถถูกระบุได้ด้วยการหาการเรียกใช้ฟังก์ชัน enterStaking() ซึ่งมีการเรียก syrup.mint() อยู่ข้างใน และการเรียกใช้ฟังก์ชัน emergencyWithdraw() ปริมาณการเรียกใช้ทั้งสองฟังก์ชันก็เป็นจุดสังเกตที่ดีต่อการตรวจสอบการโจมตีด้วย

ตัวอย่างของพฤติกรรมที่มีลักษณะสอดคล้องกับแนวทางในการโจมตีช่องโหว่

หลังจากตรวจพบการโจมตี ทีม PancakeSwap ได้มีการดำเนินการเพื่อจัดการกับสถานการณ์ที่เกิดขึ้นโดยทันที อย่างไรก็ตามการดำเนินการเพื่อจัดการปัญหานั้นไม่ปรากฎถึงการแก้ไข MasterChef contract และ SyrupBar contract ซึ่งเป็น contract ที่เกี่ยวข้อง ส่งผลให้โค้ดซึ่งทำให้เกิดเหตุการณ์นี้ยังคงอยู่ใน MasterChef contract เช่นเดิม ทีม PancakeSwap ได้อธิบายถึงการตัดสินใจนี้เอาไว้โดยพูดถึงผลกระทบที่จะเกิดขึ้นกับผู้ใช้งาน PancakeSwap ทุกคน

การแก้ไขปัญหาของ PancakeSwap แม้จะดีต่อตัวแพลตฟอร์มเอง แต่การตัดสินใจที่จะไม่แก้ MasterChef contract นั้นทำให้ความเสี่ยงที่เกิดจาก MasterChef contract ยังคงอยู่ และทำให้การ fork กลายเป็นเรื่องที่เสี่ยงได้

การมีอยู่ของโค้ดซึ่งเกี่ยวข้องกับเหตุการณ์นี้คงไว้ซึ่งความเสี่ยงที่ควรถูกพูดถึงแม้จะไม่สร้างผลกระทบโดยตรงสำหรับแพลตฟอร์ม DeFi ที่ fork โค้ดไปพัฒนาต่อยกเว้นแต่ในกรณีที่แพลตฟอร์มจะมีการเข้าไปยุ่งเกี่ยวกับ $SYRUP เรามองว่าประเด็นในส่วนนี้เป็นประเด็นที่ควรถูกนำมาสร้างความตระหนักรู้เพื่อลดโอกาสที่จะทำให้เกิดข้อผิดพลาดในอนาคตครับ

The “Less Reward” Bug

สำหรับปัญหาสุดท้ายที่เราจะมาพูดถึงกันในครั้งนี้นั้นคือปัญหาที่พบได้จาก MasterChef contract ภายใต้กลไกของการยอมให้มีการวางเหรียญสกุลเดียวกับเหรียญซึ่งเป็นผลตอบแทนครับ

แพลตฟอร์ม DeFi โดยส่วนใหญ่มีการเปิด pool ให้ผู้ใช้งานนำเหรียญมาวางเพื่อรับผลตอบแทน โดยเจ้าของแพลตฟอร์มสามารถเลือกเปิด pool สำหรับเหรียญใดก็ได้ แต่มักเปิดให้ใช้ LP token ที่ได้รับจากการเป็น liquidity provider มาวางเพื่อสร้างอรรถประโยชน์ของเหรียญนั้น

การคำนวณผลตอบแทนของ pool จะต้องมีการนำตัวแปรอย่างปริมาณของเหรียญที่ถูกนำมาวางไว้มาคิด ทำให้มีโอกาสผิดพลาดในการคำนวณได้ หากปริมาณที่มีการนำมาใช้นั้นไม่ถูกต้อง

เราขอนำตัวอย่างของกระบวนการนี้มาจาก MasterChef ของ SushiSwap ในฟังก์ชัน updatePool() ปริมาณของเหรียญที่ผู้ใช้มาวางไว้จะถูกเก็บอยู่ในตัวแปร lpSupply ที่ได้จากการดูปริมาณเหรียญของ pool นั้น ๆ ใน MasterChef contract หากมีการเปิด pool ที่อนุญาตให้ใช้เหรียญสกุลเดียวกันกับผลตอบแทนมาวาง เช่นวางเหรียญ $A เพื่อรับผลตอบแทนเป็น $A ปริมาณของ lpSupply ก็จะเพิ่มขึ้นอย่างไม่ถูกต้องจากผลตอบแทนที่ถูกสร้างขึ้นมาเก็บไว้ใน MasterChef contract เรื่อย ๆ และส่งผลให้ผลตอบแทนจากการการนำค่า lpSupply ไปใช้เป็นตัวแปรมีค่าไม่ถูกต้องตามไปด้วย

ผลลัพธ์ของปัญหานี้กระทบโดยตรงกับผลประโยชน์ที่ผู้ใช้แพลตฟอร์ม DeFi พึงได้จากตัวแพลตฟอร์มเอง เพราะในทันทีที่การคำนวณในขั้นตอนนี้ผิดพลาด ผลประโยชน์ที่ผู้ใช้แพลตฟอร์ม DeFi ควรจะได้จะน้อยลงทันที และจะน้อยลงตามผลตอบแทนที่ยังไม่ได้ถูกนำเอาออกไปจาก MasterChef contract โดยผู้ใช้คนอื่น ๆ

สำหรับนักพัฒนา ปัญหานี้สามารถแก้ไขได้หลายวิธีการ ทั้งการตรวจสอบสกุลของเหรียญที่อนุญาตใน pool ก่อน หรือในกรณีที่การวางเหรียญในสกุลเดียวกับผลตอบแทนมีความจำเป็นสำหรับแพลตฟอร์มนั้น ๆ นักพัฒนาก็สามารถใช้วิธีแยก contract ซึ่งมีหน้าที่ในการเก็บผลตอบแทนเป็น contract ใหม่ เพื่อไม่ให้ปริมาณของเหรียญถูกคำนวณผิดได้

What Can We Do?

เพราะต้นกำเนิดของปัญหาและความเสี่ยงเหล่านี้มาจากการ fork และใช้งาน MasterChef contract ซึ่งมีปัญหา การตรวจสอบ MasterChef contract ก่อนตัดสินใจใช้งานแพลตฟอร์มถือเป็นทางเลือกหนึ่งที่ทำให้เราสามารถประเมินความเสี่ยงของแพลตฟอร์มได้ด้วยตัวเอง ในการตรวจสอบ Masterchef contract เว็บไซต์ RugDoc ได้มีการรวบรวมวิธีการและแนวทางเอาไว้ ซึ่งสามารถปฏิบัติตามได้ตามลำดับดังนี้ครับ

  1. วิธีในการระบุหา MasterChef contract และการนำมาระบุหาความแตกต่างกับ MasterChef contract ที่น่าจะเป็นต้นฉบับเพื่อดูว่ามีการแก้ไขอย่างไรบ้าง (ดูเพิ่มเติม)
  2. ตัวอย่างของลายเซ็นต์หรือจุดที่เป็นเอกลักษณ์ของ MasterChef contract ที่อาจช่วยให้เราระบุได้ว่า MasterChef contract ที่เราตรวจสอบอยู่นั้นถูก fork มาจากแหล่งใด (ดูเพิ่มเติม)
  3. ตัวอย่างของโค้ดที่มักถูกเพิ่มเข้าไปใน MasterChef รวมไปถึงโค้ดที่ใช้เพื่อโจมตี สำหรับการนำมาใช้เพื่อระบุหาการแก้ไข MasterChef contract ได้ (ดูเพิ่มเติม)

แม้ว่าเราจะผ่อนถ่ายภาระของตัวเองที่จะต้องตรวจสอบ MasterChef contract ไปให้กับผู้ให้บริการด้านการตรวจสอบ smart contract และเลือกที่จะเชื่อผลลัพธ์ของการถูกตรวจสอบได้ การเข้าใจอำนาจและความรับผิดชอบต่อตัวเองซึ่งเป็นคุณสมบัติของระบบแบบ Decentralized ที่ให้เรามานั้นถือว่าเป็นเรื่องที่สำคัญยิ่งกว่า หวังว่าบทความนี้จะช่วยให้เราเข้าใจปัญหา ความเสี่ยงและสิ่งที่เราทำได้ในฐานะของพลเมืองของโลก DeFi อย่างเป็นประโยชน์ครับ

About Inspex

Inspex คือบริษัทที่เกิดจากการรวมตัวของทีมงานผู้มีประสบการณ์ทางด้าน cybersecurity ในหลากหลายสาขา เพื่อให้บริการต่าง ๆ ที่เกี่ยวข้องกับเทคโนโลยี blockchain และ smart contract โดยมีวัตถุประสงค์หลักเพื่อผลักดันระดับความมั่นคงปลอดภัยของแพลตฟอร์มต่าง ๆ รวมถึงภาพรวมของ blockchain ecosystem ด้วยการบริการที่มีคุณภาพ

สอบถามข้อมูลเพิ่มเติมได้ทาง Twitter, Telegram, contact@inspex.co

Cybersecurity professional service, specialized in blockchain and smart contract auditing https://twitter.com/InspexCo

Cybersecurity professional service, specialized in blockchain and smart contract auditing https://twitter.com/InspexCo