Ethereum Smart Contracts with Next.js [Part 1] (เริ่มต้นเขียน Smart Contracts)
จากบทความที่แล้ว เราก็ได้รู้จักเทคโนโลยีและเครื่องมือต่างๆ ที่ใช้ในการเขียน Smart Contracts กันไปแล้วในบทความนี้ (Part 1) เราจะมาเริ่มเขียน Smart Contracts ด้วยภาษา Solidity กัน
โดยเราจะเขียน Smart Contracts ของ Todo List ง่ายๆ เพื่อให้เข้าใจ Syntax ของภาษา Solidity ซึ่งจะมีฟีเจอร์ดังนี้
- สร้าง Task ได้
- Toggle Task ได้
- เรียกดู Task ทั้งหมดได้
เริ่มจากระบุ Version ของ Solidity ที่ต้องการ
pragma solidity ^0.4.24;
สร้าง Contract ชื่อว่า TodoList (contract
เปรียบเสมือน class ใน Javascript)
contract TodoList {}
สร้าง Struct สำหรับเก็บค่าในแต่ละ task ซึ่งจะประกอบด้วยค่า id, date, content, done (struct
มองง่ายๆว่าเป็น Object ใน Javascript)
contract TodoList {
struct Task {
uint id;
uint date;
string content;
bool done;
}
}
สร้าง lastTaskId สำหรับเก็บ taskId ล่าสุด
สร้าง Array taskIds สำหรับเก็บ taskId ที่มีทั้งหมด
และสร้าง mapping สำหรับ map id ของ taskId กับ struct ของแต่ละ task
(mapping
ใน Solidity คือ Hash table ที่เป็น key-value pair)
contract TodoList {
...
uint private lastTaskId;
uint[] private taskIds;
mapping(uint => Task) private tasks;
}
สร้าง event TaskCreated สำหรับส่ง event ออกไปเมื่อมีการ สร้าง task
สร้าง event TaskStatusToggled สำหรับส่ง Event ออกไปเมื่อมีการ Toggle task (โดยเราจะสร้าง Event ไว้สำหรับ Emit event ที่เกิดขึ้นออกไปภายนอก Ethereum)
contract TodoList {
...
event TaskCreated(uint id, uint date, string content);
event TaskStatusToggled(uint id, bool done);
}
สร้าง Function createTask ซึ่งจะรับ _content เข้ามา และประกาศเป็น Public เพื่อให้เรียกใช้จากที่ไหนก็ได้ แล้ว Mapping key กับ Struct Task จากนั้นเพิ่ม id เข้าไปใน Array taskIds แล้ว Emit event ออกมา
contract TodoList {
...
function createTask(string _content) public {
lastTaskId++;
tasks[lastTaskId] = Task(lastTaskId, now, _content, false);
taskIds.push(lastTaskId);
emit TaskCreated(lastTaskId, now, _content);
}
}
สร้าง Modifier ไว้สำหรับใช้เช็คเงื่อนไขในแต่การทำงานของแต่ละ function
โดย taskExist จะเช็คว่า id ที่รับเข้ามา มีค่าอยู่ใน mapping ของ tasks หรือไม่ ถ้ามีก็ให้ทำงานต่อไป แต่ถ้าไม่มีมันจะ throw error ออกมาให้
contract TodoList {
...
modifier taskExist(uint id) {
require (tasks[id].id != 0);
_;
}
}
สร้าง function สำหรับ toggle task โดยจะรับ id เข้ามาแล้ว เช็คกับ modifier taskExist ก่อน ถ้ามีก็ให้ทำฟังก์ชันต่อได้ และประกาศให้เป็น public จากนั้นจะทำการเปลี่ยน flag done แล้วให้ emit event ออกมา
contract TodoList {
...
function toggleTaskStatus(uint id) taskExist(id) public {
tasks[id].done = !tasks[id].done;
emit TaskStatusToggled(id, tasks[id].done);
}
}
สร้าง function getTaskIds สำหรับดึงค่า taskId ทั้งหมด และ ประกาศเป็น public ในการ return ต้องระบุ type ที่ต้องการ return ออกไปด้วยเสมอ
(view หมายถึงประกาศว่า function นี้ จะไม่มีการทำ action ใดๆ กับ state ภายใน Ethereum ซึ่งจะไม่เสีย gas)
contract TodoList {
...
function getTaskIds() public view returns(uint[]) {
return taskIds;
}
}
สร้าง function getTask สำหรับดึง task by id
contract TodoList {
...
function getTask(uint id) taskExist(id) public view returns(uint, uint, string, bool) {
return (id, tasks[id].date, tasks[id].content, tasks[id].done);
}
}
หลังจากนั้นจะได้ code solidity หน้าตาแบบนี้
pragma solidity ^0.4.24;contract TodoList {
struct Task {
uint id;
uint date;
string content;
bool done;
}uint private lastTaskId;
uint[] private taskIds;
mapping(uint => Task) private tasks;event TaskCreated(uint id, uint date, string content);
event TaskStatusToggled(uint id, bool done);function createTask(string _content) public {
lastTaskId++;
tasks[lastTaskId] = Task(lastTaskId, now, _content, false);
taskIds.push(lastTaskId);
emit TaskCreated(lastTaskId, now, _content);
}function toggleTaskStatus(uint id) taskExist(id) public {
tasks[id].done = !tasks[id].done;
emit TaskStatusToggled(id, tasks[id].done);
}function getTaskIds() public view returns(uint[]) {
return taskIds;
}function getTask(uint id) taskExist(id) public view returns(uint, uint, string, bool) {
return (id, tasks[id].date, tasks[id].content, tasks[id].done);
}modifier taskExist(uint id) {
require (tasks[id].id != 0);
_;
}
}
ซึ่งเราสามารถนำ code Solidity ไปทดสอบได้ที่ https://remix.ethereum.org/
ให้กดปุ่ม Deploy แล้วจะสามารถทดสอบ function ที่เราสร้างขึ้นมาได้
- ลอง createTask โดยส่ง string คำว่า “Arm” ลงไป
- ลอง toggleTaskStatus โดยส่ง uint 1 ลงไป
- ลอง getTask โดยใส่ uint 1 ลงไป
- ลอง getTaskIds
จะเห็นว่า ตอน getTask จะมีค่า content ว่า “Arm” ที่เราใส่ลงไป พร้อมทั้ง flag “true” ที่เราทำการเปลี่ยน หลังจากนั้นใน getTaskIds ก็จะมี id ของ todolist ของเราทั้งหมด โดยจะเริ่มจาก 1 เสมอ
แต่ถ้าลองกดปุ่ม Deploy ใหม่แล้วใส่ 0 ลงไปตรง function getTask แล้วลองกดปุ่ม getTask ตัว ide ก็จะ throw error ออกมาผ่านทาง console เพราะไม่ผ่านเงื่อนไขของ modifier ที่เราได้สร้างเอาไว้
หลังจากที่ลองเขียน Solidity TodoList และทดสอบด้วย remix ide แล้ว เราก็ได้รู้จัก Syntax คร่าวๆของ Solidity ไปบ้างแล้ว เรามาลอง deploy code ของเราขึ้น localhost กันต่อเลย
เริ่มจากติดตั้ง truffle framework และสร้าง truffle project โดยพิม command ดังนี้
npm install -g truffle
mkdir ethereum-todolist && cd ethereum-todolist
truffle init
จะได้ project structure หน้าตาแบบนี้
ใน folder contracts ให้เราสร้างไฟล์ชื่อ TodoList.sol แล้ว copy code ที่ทดสอบใน remix ide วางลงไปแล้วไปแล้วทำการ save
ใน folder migrations ให้เราสร้างไฟล์ชื่อ 2_deploy_contracts.js แล้วใส่ code ลงไปดังนี้ (เพื่อบอกให้ truffle compile Smart Contracts ที่เราเขียนขึ้นมา)
const TodoList = artifacts.require('TodoList.sol')
module.exports = function(deployer, network, accounts) {
return deployer.then(() => {
return deployer.deploy(TodoList)
})
}
จากนั้นในหน้า command ให้พิมพ์
truffle develop
จะเห็นหน้า console แบบนี้
จะสังเกตว่า Smart Contracts ของเราจะรันอยู่บน localhost:9545 (ค่า default)
จากนั้นให้พิมพ์ compile
ตรงช่อง truffle(develop)> จากนั้นเราจะเห็น folder build เพิ่มขึ้นมาดังนี้
โดยใน folder build จะมีไฟล์ TodoList.json ที่ truffle ทำการ build มาให้ ซึ่งในไฟล์ json นี้จะมีค่าที่เราจำเป็นต้องเอาไปใช้ 2 ค่าหลักๆ ก็คือ Application Binary Interface (abi) และ bytecode
ค่า abi จะเป็นค่าที่เรานำไปใช้ติดต่อกับ Smart Contracts ของเรา
ค่า bytecode จะเป็นค่าสำหรับ truffle จะเอาไป deploy บน localhost หรือ network test ต่างๆ ที่เราต้องการ
จากนั้นพิมพ์ migrate
ตรงช่อง truffle(develop)> เพื่อทำการ deploy Smart Contracts TodoList ไปที่ localhost:9545
(ถ้ามีการแก้ไขไฟล์ TodoList.sol และต้องการ deploy ใหม่ ให้ใช้ migrate --reset
เพื่อให้ลบ folder build เดิมออกก่อน)
เมื่อ deploy เสร็จเราจะได้ address ของ Smart Contracts ดูได้ตรงหน้า terminal ของเรา (ค่า address จะเปลี่ยนใหม่เสมอ ทุกครั้งที่เราทำการ deploy)
TodoList: 0x345ca3e014aaf5dca488057592ee47305d9b3e10
ซึ่งจะถูกนำไปใช้กับ Web3.js
ในบทความนี้เราก็ได้รู้จัก Syntax ของ ภาษา Solidity กันไปบ้างแล้ว ถ้าอยากรู้แบบจัดเต็มสามารถอ่านได้ที่ Doc ของ Solidity https://solidity.readthedocs.io/en/v0.4.24/index.html หรือ ถ้าการอ่าน Doc มันน่าเบื่อ สามารถไปเล่นเกม เพื่อทำความเข้าใจเกี่ยวกับภาษาได้ที่ https://cryptozombies.io/en/course
ในการเขียน Smart Contracts สิ่งที่ควรคำนึงถึงมากที่สุดก็คือ เขียนยังไงให้ปลอดภัยเมื่อ code ที่เราเขียนทุกคนสามารถเข้าถึงได้ และ เขียนยังไงให้ใช้ ค่า gas น้อยที่สุด
หลังจากที่เรา deploy Smart Contracts ไปที่ localhost:9545 แล้ว บทความหน้า (Part 2) เราจะนำ Next.js มาเชื่อมต่อกับ Smart Contracts ของเรากัน