[Libra & Move] ทำไมภาษา Move จึงเป็นหนึ่งภาษาสำหรับเขียน Token Smart Contract ที่ดีที่สุดในตอนนี้
อย่างที่หลาย ๆ คนคงจะทราบแล้วว่า Libra คือ Blockchain Platform และ Token ใหม่ที่ก่อตั้งโดย Facebook ร่วมกับพาร์ทเนอร์ชื่อดังหลาย ๆ เจ้าอย่างเช่น Visa, Paypal, Stripe, Ebay และอื่น ๆ
ตัว Libra Core นั้นถูกเขียนโดยภาษา Rust ของ Mozilla เว็บเบราเซอร์ชื่อดังซึ่งออกแบบมาเพื่อความปลอดภัยของหน่วยความจำ (Safe Memory) และการทำงานพร้อมกันหลาย ๆ Task โดยเฉพาะ (Safe Concurrency) โดยจะไม่ใช้ตัวเก็บกวาดหน่วยความจำ (Garbage Collector) แบบภาษาอื่น ๆ อย่างเช่น Java ซึ่งถ้าหลาย ๆ คนที่เคยใช้โทรศัพท์ Android อาจจะเคยพบปัญหาการกระตุกเป็นระยะ นั่นเกิดขึ้นเมื่อ Garbage Collector กำลังทำงาน ทำให้ลดประสิทธิภาพของระบบลงชั่วคราว แต่เมื่อ Rust ไม่มี Garbage Collector จึงทำให้การทำงานของระบบมีประสิทธิภาพขึ้นอีกด้วย
Libra นั้นมาพร้อมกับภาษา Move ซึ่งเป็นเหมือนสำเนียงของภาษา Rust อีกที ซึ่งออกแบบมาเพื่อความปลอดภัยของการย้ายความเป็นเจ้าของของ Token โดยยืมคอนเส็ปต์การทำงานของคำสั่ง move() ในภาษา Rust มาใช้อีกที สังเกตได้จากชื่อของภาษาเอง
ก่อนที่จะอธิบายเรื่อง Move ขอเล่าถึงปัญหาของภาษาที่ใช้เขียน Smart Contract ในปัจจุบันก่อน
Solidity
ภาษาที่ใช้ในการเขียน Smart Contract ที่โด่งดังและมีอิทธิพลมากที่สุดนั้นจะเป็นใครไปไม่ได้เลยนอกจากภาษา Solidity บน Ethereum
เดิมทีภาษา Solidity นั้นถูกออกแบบมาเพื่อทำ Smart Contract ก็จริง แต่ก็มีจุดมุ่งหมายเพื่อที่จะเป็นคอมพิวเตอร์ของโลก ไม่ได้ตั้งใจให้เอาไว้เขียน Token โดยเฉพาะ การเขียน Token บน Solidity นั้นพึ่งจะเริ่มโด่งดังเมื่อมีคนนำเสนอ ERC-20 Protocol เพื่อเป็นมาตรฐานในการเขียน Token บน Solidity และกลับกลายเป็นว่ากว่า 99% ของ Ethereum Smart Contract ในปัจจุบันนั้นใช้ในการเขียน Token เนื่องจากความนิยมในการทำ ICO ในปี 2016-2017
การพยายามนำ Solidity ที่ไม่ได้ออกแบบมาเพื่อความปลอดภัยของ Token มาทำ Token นั้นจึงมีความเสี่ยงมากมายของความผิดพลาด เช่น การ Double Spending, การสูญเสีย Token ยังเกิดขึ้นได้หาก Smart Contract ที่เขียนไว้นั้นป้องกันได้ไม่ดีพอ
uint public totalSupply = 0;
mapping(address => uint) balances;/**
* @dev transfer token for a specified address
* @param _to The address to transfer to.
* @param _value The amount to be transferred.
*/
function transfer(address _to, uint _value) onlyPayloadSize(2 * 32) {
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
Transfer(msg.sender, _to, _value);
}
ลองพิจารณา ERC-20 Token Smart Contract ด้านบนซึ่งเขียนโดยภาษา Solidity จะมี mapping ตัวหนึ่งเพื่อบอกปริมาณเงินของ Address ใด ๆ ซึ่งเก็บเป็น unsigned integer
เมื่อ User ต้องการ Transfer Token ตัว Smart Contract ก็จะทำการลบ balance จาก balances[msg.sender] แล้วไปเพิ่มให้ balances[_to] หรือลบเงินจาก Account หนึ่งแล้วไปเพิ่มให้อีก Account หนึ่ง
การแสดง Token เป็น Integer นั้นก่อให้เกิดปัญหาความปลอดภัยบางอย่างได้ เนื่องจาก Integer ใน Mapping แต่ละ Key นั้นแยกขาดกันอย่างสิ้นเชิง หาก Smart Contract นั้นเขียนไว้ไม่ดีก็อาจจะมีเงินที่ขาดหายไปหรืองอกขึ้นมาใหม่จากอากาศทำให้ Token รวมในระบบไม่เท่ากับจำนวน totalSupply ได้
จะเกิดอะไรขึ้นเมื่อมีการลบเงินจาก Account หนึ่ง สำเร็จแต่การเพิ่มเงินในอีก Account หนึ่งผิดพลาด Ethereum ใช้ความ Atomicity ของ Transaction ในการจัดการเรื่องนี้คือ หาก Operation ใด ๆ ใน Transaction ทำงานผิดพลาดก็จะยกเลิกการทำงานทั้งหมด
แต่ Libra มองแตกต่างไปจากนั้น Libra มองว่าการแสดง (Represent) Token ด้วย Integer นั้นไม่ปลอดภัยเพียงพอ การ Represent Token จำเป็นต้องมี Syntax ของภาษาเพื่อจัดการเรื่องนี้โดยเฉพาะ เพื่อให้ปริมาณ Token ทั้งหมดนั้นเท่ากับจำนวน totalSupply เสมอ Token ทุก Token ต้องมีเจ้าของเสมอ การโอนเงินควรจะเป็นการย้ายความเป็นเจ้าของของ Token อย่างชัดเจนไม่ใช่แค่การลบเลขออกจาก Account หนึ่งแล้วไปบวกเพิ่มให้อีก Account หนึ่ง
move() ในภาษา Rust
move() นั้นเป็นคอนเส็ปต์ของการย้ายทรัพยากรไปให้ออบเจ็คท์อื่นอย่างมีประสิทธิภาพซึ่งมีมาตั้งแต่ในภาษา C++ ซึ่งภาษา Rust ได้หยิบยืมมาใช้เพื่อจัดการ Memory โดยไม่ต้องใช้ Garbage Collector
ยกตัวอย่าง String Object s1 หนึ่งที่มีค่า “hello” ซึ่ง Data Structure ของมันประกอบไปด้วย ขนาดของ String, ความจุของ String และ Pointer ที่ชี้ไปที่ address ที่เก็บ String ใน heap
let s1 = String::from("hello");
let s2 = s1; println!("{}, world!", s1);
หากเราประกาศ String Object s2 ให้มีค่าเท่ากับ s1 เราอาจออกแบบภาษาหรือ Data Structureให้ pointer ของ s2 ชี้ไปที่เดียวกันกับ s1 เพื่อลดปริมาณข้อมูลใน Heap เนื่องจากข้อมูลเป็นอันเดียวกัน แต่ปัญหาของการออกแบบนี้คือเมื่อโปรแกรมไม่ต้องการใช้ string “hello” อีกต่อไป เช่นการปิด scope ตัวแปร s1 และ s2 ก็จะพยายามลบข้อมูลที่ pointer ของมันชี้ไปหาซึ่งก็จะทำให้เกิดปัญหาที่เรียกว่า double free error
ซึ่งอาจทำให้เกิด memory corruption
Rust แก้ปัญหานี้ด้วยการย้าย Ownership ไปเลย โดยสร้าง pointer ใหม่ให้ s2 และทำลาย pointer ของ s1 และเมื่อใดที่หมด scope ก็จะมีแค่ s2 เท่านั้นที่ไปเคลียร์ข้อมูลออกจาก heap ทำให้ปัญหา double free error
หมดไป
นี่คือสิ่งที่เรียกว่า move() ซึ่งมีอิทธิพลในการสร้างภาษา Move เพื่อเคลื่อนย้าย Token โดยให้มี Owner เพียงคนเดียวและมีความ Atomicity ในเวลาต่อมา
ภาษา Move
ภาษา Move นั้นจัดให้ Token/Coin นั้นเป็น First Class Resource ซึ่งเมื่อต้องการย้าย Ownership ก็ต้องใช้การ move() เสมอ
พิจารณาโค้ดด้านบน ตัวแปร coin เป็น Resource Type : Coin ที่ได้มาจาก Module Currency ของ Address 0x0 ซึ่งดึงมาจากฟังก์ชัน withdraw_from_sender ซึ่งเรียกได้โดย Owner ของ Coin นั้น
เมื่อเราได้ Coin มาแล้วเราจะต้องทำการ move(coin) เข้าไปสู่ผู้รับโดยเรียกฟังก์ชันของ Module Currency ชื่อ deposit
ซึ่งโดยตัวภาษา Move เองจะป้องกันความผิดพลาดไว้ดังนี้
- จะเปลี่ยน move(coin) เป็น copy(coin) ไม่ได้ เนื่องจาก resource ต้องใช้การ move เท่านั้น
- จะ move(coin) สองครั้งไม่ได้ เนื่องจาก resource ต้องไปมี Owner เพียงคนเดียว หากเทียบกับภาษา Solidity นั้น เราอาจไปเพิ่มเงินให้ผู้ใช้ 2 คนได้โดยการลบเงินออกจากคน ๆ เดียว หรือไม่ลบเงินออกจากผู้ส่งเลยก็ได้เพราะมันเป็นเพียง
integer
- จะไม่ move(coin) ไม่ได้หาก withdraw ออกมาแล้ว ซึ่งภาษา Move ใส่มาเพื่อป้องการการลืมโดยตั้งใจหรือไม่ได้ตั้งใจของผู้เขียน Smart Contract
จะเห็นได้ว่าภาษา Move มีการป้องกันความผิดพลาดของการย้าย Token/Coin อย่างแน่นหนาโดยยืมแนวคิดการ Move Pointer และ Ownership ของ Rust มาใช้ ซึ่งทำให้เราสามารถเขียน Smart Contract สำหรับ Token ได้อย่างปลอดภัยโดยไม่ต้องกังวลเรื่องความผิดพลาดจากการใช้ integer
อ้างอิง