หลายๆ คนคงเคยได้ยินเหรียญโทเคน ERC20 กันดีถ้าเริ่มเข้าสู่วงการคริปโต ERC20 มันเป็นรากฐานสำคัญของ DeFi เลยก็ว่าได้ อาจจะรู้ว่ามันเป็น smart contract แบบนึงแต่ไม่เคยรู้ว่ามันทำงานยังไง ทั้งที่มันไม่ได้ซับซ้อนนัก วันนี้ผมก็จะเอาโค้ดของมันมาอธิบายให้เข้าใจกันง่ายๆ เทียบกับการเก็บข้อมูลบนสเปรดชีต เอ็กเซลที่เรารู้จักกันนี่แหละ คนทั่วใปจะได้เข้าใจมันง่ายๆ
เทียบกันตรงๆ การเก็บข้อมูลใน ERC20 smart contract ก็เหมือนกับเอ็กเซลไฟล์นึงนี่แหละ เพื่อให้เข้าใจง่ายๆ ผมจะแบ่งออกเป็น 4 ส่วน ดังนี้
- ข้อมูลทั่วไปที่ไม่มีการเปลี่ยนแปลง (info)
- ข้อมูลจำนวนเหรียญ (balances)
- ข้อมูลจำนวนเหรียญที่อนุญาตให้คนอื่นใช้ (allowances)
- บันทึกเหตุการที่เกิดขึ้นกับข้อมูลต่างๆ (events)
1 ข้อมูลทั่วไปที่ไม่มีการเปลี่ยนแปลง
ในที่นี้จะเป็นตัวแปรต่างๆ ได้แก่ ชื่อเหรียญ(name) ตัวย่อ(symbol) จำนวนหลักตัดจุดทศนิยม(decimals) จำนวนเหรียญทั้งหมด(totalSupply) ข้อมูลเหล่านี้อาจเปลี่ยนแปลงได้ก็ได้ แต่ส่วนใหญ่จะไม่มีการเปลี่ยนแปลง เอาไว้ใช้อ้างอิง
จากโค้ด
string public name;
string public symbol;
uint public decimals;
uint public _totalSupply;
จะได้ซีตหน้าตาเป็นแบบนี้
โดยตัวเลขมูลค่าใน smart contract จะเก็บเป็นจำนวนเต็ม เวลาอ้างถึง 1 หน่วยก็จะตัดทศนิยมตาม decimals ตัวอย่างเช่น เก็บค่า 987654321 ตัดที่ decimals=8 ก็จะได้ ตัดทศนิยมที่หลักที่ 8 มูลค่าที่แสดงผลจะเป็น 9.87654321 หน่วยเท่านั้น เวลาคำนวนจะใช้เลขจำนวนเต็มไม่มีการปัดเศษ
2 ข้อมูลจำนวนเหรียญ (balances)
ชีตนี้จะเป็นตารางเก็บจำนวนเงินที่แต่ละ address มี เป็นยอดล่าสุดเท่านั้นไม่มีรายการย้อนหลัง
ในโค้ดจะเขียนเป็น
mapping(address => uint256) private _balances;
สมมุติให้ตอนเริ่มต้นสร้างเหรียญมา 4,321,000,000,000 จะโอนไปให้กระเป๋าคนสร้าง (0xAA) ทันที พร้อมทั้งเพิ่มค่า totalSupply หน้าตาตารางเก็บข้อมูลจะเป็นแบบนี้
แล้วถ้า 0xAA โอน 100 หน่วย (10,000,000,000) ให้ 0xBB ล่ะ สิ่งที่จะเกิดขึ้นก็คือ จะหัก 100 หน่วยออกจาก 0xAA แล้วเพิ่ม 0xBB 100 ในตาราง หน้าตาตารางเก็บข้อมูลจะเป็นแบบนี้
อยากจะรู้ว่ากระเป๋าไหนมีเหรียญเท่าไหร่ก็ฟิลเตอร์เอาตาม address นั้นๆ เวลาใช้งานก็จะเรียกฟังก์ชั่น transfer(recipient, amount) และมีการเพิ่ม event ว่าเกิดอะไรขึ้นด้วยซึ่งจะอธิบายในส่วน ที่ 4 บันทึกเหตุการที่เกิดขึ้นกับข้อมูลต่างๆ (events)
3 ข้อมูลจำนวนเหรียญที่อนุญาตให้คนอื่นใช้ (allowances)
หลังจากที่มีที่เก็บข้อมูลจำนวนเหรียญของแต่ละคนแล้ว ทีนี้มันมีปัญหาอยู่ว่าเวลาที่จะให้ smart contract อื่นๆ เข้ามายุ่งกับจำนวนเหรียญของเจ้าของกระเป๋าเราได้โดยที่เราไม่ได้ทำทรานเซคชั่นนั้นเองมันทำไม่ได้ แต่ในบางกรณีก็จำเป็น ยกตัวอย่างเช่นการ swap เหรียญ ที่จะมีกระเป๋าเดียวที่ทำทรานเซคชั่นเท่านั้น แต่จะเกิดการโอนของสองเหรียญจากสองกระเป๋าในทรานเซคชั่นเดียวกัน โดยสิ่งที่ต้องทำก่อน swap ก็คือ approve อนุญาตให้ swap contract สั่งโอนเหรียญเราทีหลังได้นั่นเอง เป็นที่มาว่าทำไมต้องเก็บข้อมูล allowances
จากในโค้ดจะเทียบเป็นตารางซ้อนตาราง
mapping(address => mapping(address => uint256)) private _allowances;
ตัวอย่างเช่น 0xAA approve อนุญาต ให้ 0xDD กับ 0xFF สั่งโอนเหรียญได้ 10 หน่วย (1,000,000,000)
โดย สมมุติว่า 0xDD เป็น smart contract ง่ายๆ ที่เขียนขึ้นมาฝากขายเหรียญ ERC20 นี้โดยคิดโอน ETH มา 1 จะได้ 1 Token จากคนที่ขายกลับไป 0xAA มีเหรียญอยู่ใน balance ตัวเอง ไม่จำเป็นต้องโอนเหรียญออกไปฝากไว้เพื่อวางขายที่ไหน แค่ approve ให้ 0xDD สามารถโอนออกเหรียญไปจาก balance ตัวเองได้โดยตรง โดย smart contract 0xDD เรียกใช้งานฟังก์ชั่น transferFrom(sender, recipient, amount) การทำงานคล้าย transfer ปกติแต่หักยอดจาก sender เมื่อเรียกใช้งานแล้วจำนวน allowance ก็จะลดลงตามจำนวนที่ใช้งานไป
approve มีสองแบบคือแบบระบุตามจำนวนที่จะให้ใช้และลดลงตามจำนวนที่ใช้และ แบบ max ใช้ได้ไม่จำกัดจำนวนเหรียญ
allowances ยังเปิดโอกาสให้ smart contract สามารถทำ logic อื่นๆ ได้ด้วย เช่น คำนวณอัตราแลกเปลี่ยน ยืมเหรียญ โอนเหรียญต่อหลายๆ ทอด เป็นต้น allowances จึงเป็นอะไรที่สำคัญมากๆ เป็นรากฐานสำคัญของ DeFi เลยก็ว่าได้
อย่างไรก็ตามการ approve เหรียญให้ address อื่นโดยไม่ดูดีๆ อาจจะโดนขโมยเหรียญได้
4 บันทึกเหตุการที่เกิดขึ้นกับข้อมูลต่างๆ (events)
ส่วนนี้ไม่ใช่การบันทึกสถานะล่าสุดของข้อมูล เป็นเหตุการที่เกิดขึ้นเรื่อยๆ เช่น จำนวนเหรียญสุดท้ายมี 100 หน่วย อาจจะมีการโอนเข้า 100 ครั้ง โอนออก 5 ครั้งก็ได้ ใน ERC20 จะมี event สองแบบคือ Transfer กับ Approval คืออะไรก็ตามที่ทำการเปลี่ยนแปลงข้อมูล balances กับ allowances จะทำให้เกิด event ออกมา
โค้ดของ Event Transfer
event Transfer(address indexed from, address indexed to, uint256 value);
โค้ดของ Event Approval
event Approval(address indexed owner, address indexed spender, uint256 value);
เลยจะแบ่งเป็น 2 ซีท ตาม เหตุการณ์ตัวอย่างทั้งหมดข้างต้น และ event จะมีข้อมูลหมายเลข block ใช้เป็น timestamp เรียงลำดับตามเหตุการณ์ที่เกิดขึ้น ไม่มีการกับไปแก้ข้อมูลเดิมจะเพิ่มไปเรื่อยๆ ต่างกับ balances และ allowances ที่มีการอัพเดตข้อมูลได้
สรุป
ERC20 สามารถทำความเข้าใจได้ง่ายๆ โดยเทียบได้กับไฟล์ Excel ไม่มีอะไรยุ่งยาก แต่อะไรง่ายๆ แบบนี้เป็นพื้นฐานของ smart contract ที่ซับซ้อนได้
ไฟล์
https://docs.google.com/spreadsheets/d/19YSbpgLXknOIaF90e49LjDzvTVhk6T5sPiAOM8n3bKg/edit?usp=sharing