Ethereum Smart Contracts with Next.js [Part 1] (เริ่มต้นเขียน Smart Contracts)

Supakorn Thongtra
Dcen.io
Published in
5 min readJun 1, 2018
รูปภาพที่เกี่ยวข้อง
https://blog.rublix.io/how-smart-contracts-can-improve-revenue-sharing-in-the-21st-century-8a2294533efa

จากบทความที่แล้ว เราก็ได้รู้จักเทคโนโลยีและเครื่องมือต่างๆ ที่ใช้ในการเขียน 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/

remix-ide

ให้กดปุ่ม Deploy แล้วจะสามารถทดสอบ function ที่เราสร้างขึ้นมาได้

test 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 ที่เราได้สร้างเอาไว้

throw error

หลังจากที่ลองเขียน 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 ของเรากัน

--

--