Tiếp tục loạt bài thực tế phát triển dApp, giờ chúng ta sẽ nâng độ khó lên một chút xíu khi đi vào hoàn thiện nội dung của smart contracts và viết test case kiểm thử.
Events
Các smart contracts được thực thi trong môi trường của EVM, hoàn toàn tách biệt với môi trường bên ngoài. Để có thể trigger một sự kiệu ra bên ngoài EVM ta gửi các events được định nghĩa trước:
//New job append event NewJob( uint256 indexed id, address creator, uint256 salary, uint256 timeOut); //An woker start working event TakeJob( uint256 indexed id, address indexed labor);
Các event này có thể lắng nghe thông qua các filter cung cấp bởi web3js, việc này sẽ cung cấp thêm thông tin, trigger các hệ thống third party hoặc đơn giản là điều chỉnh lại front-end.
Modifier
Để smart contract của mình bảo mật hơn, mình thêm các modifier. Mục tiêu là kiểm tra arguments trước khi method được trigger (Ethereum là một hệ hoàn toàn thụ động, không có khả năng tự tính toán).
//Transaction must contant Ethereum modifier onlyHaveFund { require(msg.value > MINIUM_SALARY); _; }
Trên đây bạn có thể đọc thấy modifier rất rõ nghĩa, với modifier này mình check số Ethereum có trong transaction. Biến msg sẽ chứa vài thông tin hữu ích của transaction (eg. msg.sender: address của người gửi, msg.value: số ethereum tính bằng wei). Để biết rõ thêm global variable các bạn xem ở đây.
Smart contract
Mình viết lại smart contract, thêm hai function mới takeJob() và viewJob():
//Take job function takeJob (uint256 jobId) public onlyValidMortgage(jobId) onlyValidId(jobId) onlyValidJob(jobId) { //Trigger event to log labor TakeJob(jobId, msg.sender); //Change working state jobData[jobId].start = block.timestamp; } //Veiw job data function viewJob(uint256 jobId) public onlyValidId(jobId) constant returns ( uint256 id, address creator, uint256 salary, uint256 start, uint256 end, uint256 timeOut, bytes title, bytes description) { Job memory jobReader = jobData[jobId]; return (jobReader.id, jobReader.creator, jobReader.salary, jobReader.start, jobReader.end, jobReader.timeOut, jobReader.title, jobReader.description); }
Trong phần struct mình cũng update thêm các biến liên quan tới thời gian, và để tiện xử lý mình sẽ sữ dụng Unix timestamp (Solidity không có kiểu datime nên dùng Unix time là tiện nhất):
uint256 start; uint256 end; uint256 timeOut;
Chúng ta có toàn bộ mã nguồn như sau:
pragma solidity ^0.4.18; contract PartTime { //Job structure struct Job { uint256 id; address creator; uint256 salary; uint256 start; uint256 end; uint256 timeOut; bytes title; bytes description; } //New job append event NewJob(uint256 indexed id, address creator, uint256 salary, uint256 timeOut); //An woker start working event TakeJob( uint256 indexed id, address indexed labor); //Minium accept salary uint256 constant public MINIUM_SALARY = 0.1 ether; //The number of jobs uint256 public totalJob; //Mapped data mapping (uint256 => Job) public jobData; //Transaction must contant Ethereum modifier onlyHaveFund { require(msg.value > MINIUM_SALARY); _; } //Valid timeOut should be greater than 3 days modifier onlyValidTimeOut(uint256 timeOut) { require(timeOut > 3 days); _; } //Check valid job Id modifier onlyValidId(uint256 jobId) { require(jobId < totalJob); _; } //Mortgage should be greater than 1/10 modifier onlyValidMortgage(uint256 jobId) { require(msg.value > jobData[jobId].salary/10); _; } //Check is it a taked job modifier onlyValidJob(uint256 jobId) { require(jobData[jobId].end == 0); require(jobData[jobId].start == 0); _; } //Append new job to mapping function createJob (uint256 timeOut, bytes title, bytes description) public onlyHaveFund onlyValidTimeOut(timeOut) payable returns(uint256 jobId) { // Saving a little gas by create a temporary object Job memory newJob; // Assign jobId jobId = totalJob; newJob.id = jobId; newJob.id = timeOut; newJob.title = title; newJob.description = description; newJob.salary = msg.value; newJob.creator = msg.sender; //Trigger event NewJob(jobId, msg.sender, msg.value, timeOut); // Append newJob to jobData jobData[totalJob++] = newJob; return jobId; } //Take job function takeJob (uint256 jobId) public onlyValidMortgage(jobId) onlyValidId(jobId) onlyValidJob(jobId) { //Trigger event to log labor TakeJob(jobId, msg.sender); //Change working state jobData[jobId].start = block.timestamp; } //Veiw job data function viewJob(uint256 jobId) public onlyValidId(jobId) constant returns ( uint256 id, address creator, uint256 salary, uint256 start, uint256 end, uint256 timeOut, bytes title, bytes description) { Job memory jobReader = jobData[jobId]; return (jobReader.id, jobReader.creator, jobReader.salary, jobReader.start, jobReader.end, jobReader.timeOut, jobReader.title, jobReader.description); } }
Và mình viết thêm test case bằng JavaScript:
var Partime = artifacts.require("./PartTime.sol"); function createTx(from, to, value = 0, gas = 1000000, gasPrice = 20000000) { return { from: from, to: to, gas: gas, gasPrice: gasPrice, value: value }; } contract('Partime', function (accounts) { it('should have 0 total part time job', function () { return Partime.deployed().then(function (instance) { return instance.totalJob.call(); }).then(function (totalJob) { assert.equal(totalJob.valueOf(), 0, 'Total job was not equal to 0'); }); }); it('should able to add new job', function () { return Partime.deployed().then(function (instance) { return instance.createJob(((new Date()).getTime() / 1000 + 432000), "This is tittle", "This is description", createTx(accounts[0], instance.address, web3.toWei('1', 'ether'))); }).then(function (totalJob) { assert.equal(typeof (totalJob.valueOf()), 'object', 'Transaction was not triggered success'); }); }); it('should have total part time job geater than 0', function () { return Partime.deployed().then(function (instance) { return instance.totalJob.call(); }).then(function (totalJob) { assert.equal(totalJob.valueOf() > 0, true, 'Total job was equal to 0' ); }); }); });
Bạn chú ý thấy mình đang thêm 1 job mới:
return instance.createJob(((new Date()).getTime() / 1000 + 432000), "This is tittle", "This is description", createTx(accounts[0], instance.address, web3.toWei('1', 'ether')));
Đoạn code này có nghĩa là mình tạo ra 1 job có
- Timeout: 5 ngày
- Title: This is tittle
- Description: This is description
- Value: 1 Ethereum
Thực thi kiểm thử:
Mình đã update source code tại https://github.com/chiro-hiro/part-time-dapp. Các bạn có thể clone về hoặc fork về viết cùng mình cho vui.
- Lập trình smart contracts: Phần 4 Bài toán thực tế
- Lập trình Smart contracts Phần 3: Xây dựng một dAPP
- Lập trình smart contracts: Phần 2 Viết smart contracts đầu tiên
- Lập trình smart contracts: Phần 1
Dislaimer: Đây là thông tin cung cấp dưới dạng blog cá nhân, không phải thông tin tổng hợp hay lời khuyên đầu tư. Chúng tôi không chịu trách nhiệm về các quyết định đầu tư của bạn.