Lập trình smart contracts: Phần 2 — Viết smart contracts đầu tiên

Dũng Trần
tradahacking
Published in
5 min readNov 22, 2017

Như đã hứa hẹn về một series dài hơi về Lập trình smart contracts, hôm nay chúng ta lại gặp nhau trong Phần 2 — Viết smart contracts đầu tiên.

Mở đầu

Smart contracts sinh ra nhằm mục đích giải quyết bài toán tin tưởng trong một môi trường thiếu tin tưởng như Internet. Và smart contract không phải là điều gì quá ghê gớm, nó sinh ra để giải quyết các bài toán thực tế, chẳng hạn như: trả lương, bảo hiểm, thừa kế, trao đổi, mua bán…. Gần đây chúng ta nghe nhiều về Decentralized Exchange, ICO… mọi người nói về nó thực sự rất ghê gớm. Đôi khi người ta thổi phồng sự việc lên một cách không cần thiết nhằm đạt mục đích PR. Nhưng trong kỹ thuật mọi thứ đều tiệm cận tới θ.

ERC20 Token

Trong hệ thống của Ethereum thì ETH đóng vai trò là native token, transaction fee sẽ được tính trên token này. Ethereum platform còn cho phép người dùng định nghĩa token riêng và để chuẩn hóa các token này, ERC20 ra đời. Tùy múc đích của người sáng lập mà một ecosystem có thể có hoặc không có token.

Viết một token đơn giản

Một token đơn giản nhất sẽ bao gồm các thành phần sau:

  • totalSupply: Tổng số token
  • balanceOf(): Hiển thị balance của một owner
  • transfer(): Chuyển token từ người sở hữu này sang cho người khác

Đầu tiên ta phải định nghĩa một mapping để lưu trữ thông tin của token đóng vai trò như một ledger:

mapping (address => uint256) balances;

Với mỗi address sẽ mapping tới một số uint256 (unsigned integer 256 bits). Ta có thể tương tác thông qua toán tử [] để access các phần tử.

Tiếp theo để đọc dữ liệu từ mapping ta tạo ra method balanceOf():

function balanceOf(address _owner)
view public
returns(uint256){
return balances[_owner];
}

Method này cho phép ta đọc thông tin của bất cứ owner nào và biết họ có bao nhiêu token.

Ta định nghĩa method transfer() để có thể chuyển đổi token giữa các owner

function transfer(address _to, uint256 _value)
external
returns(bool){
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}

Và kết hợp tất cả ta có smart contract của AmazingToken

pragma solidity >=0.5.1 <=0.6.0;contract AmazingToken{

uint256 public totalSupply;

mapping (address => uint256) balances;

function balanceOf(address _owner)
view public returns(uint256){
return balances[_owner];
}

function transfer(address _to, uint256 _value)
external returns(bool){
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}

}

Bảo mật cho AmazingToken:

Trong smart contract nói trên ta sẽ thấy các vấn đề của method transfer() đó là:

  • Nếu _to = 0x00 thì token sẽ biến mất, giống như ta stream data vào /dev/null vậy
  • Transfer vẫn thực thi cho dù msg.sender không có đủ balance

Giờ ta viết lại smart contract của amazing token kèm theo việc check các conditions này bằng cách thêm 2 modifiers onlyValidAddress onlyValidValue.

pragma solidity >=0.5.1 <=0.6.0;contract AmazingToken{

uint256 public totalSupply;

mapping (address => uint256) balances;

event Transfer(address indexed _from, address indexed _to, uint256 _value);

modifier onlyValidAddress(address _to){
require(_to != address(0x00), "Invalid address");
_;
}

modifier onlyValidValue(address _from,uint256 _value){
require(_value <= balances[_from], "Invalid value");
_;
}

function balanceOf(address _owner)
view public
returns(uint256){
return balances[_owner];
}

function transfer(address _to, uint256 _value)
onlyValidAddress(_to) onlyValidValue(msg.sender,_value)
external
returns(bool){
balances[msg.sender] -= _value;
balances[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}

}

Việc thêm hai modifier này làm cho contract của chúng ta an toàn hơn rất nhiều. Sau khi lập trình xong token thì điều mọi người nghĩ tới cách thức để phát hành token.

Định nghĩa phương thức phát hành token

Ta định nghĩa method nhằm bán AmazingToken mỗi khi có ai đó gửi Ethereum tới sẽ nhận lại AmazingToken theo tỉ lệ 1:100.

function () external payable {
uint256 _issued = (msg.value*100)/10**18;
totalSupply += _issued;
balances[msg.sender] = _issued;
}

Function nói trên được gọi là fallback function sẽ được trigger nếu không có method name nào được chỉ ra. Biến global msg chứa các thông tin về transaction msg.sender chứa thông tin người trigger smart contract. msg.value chứa giá trị eth theo đơn vị wei (1 eth = 10¹⁸ wei).

Smart contract của AmazingToken sẽ như sau:

pragma solidity >=0.5.1 <=0.6.0;contract AmazingToken{

uint256 public totalSupply;

mapping (address => uint256) balances;

event Transfer(address indexed _from, address indexed _to, uint256 _value);
event BuyToken(address indexed _buyer, uint256 _value);

function () external payable {
uint256 _issued = (msg.value*100)/10**18;
totalSupply += _issued;
balances[msg.sender] = _issued;
emit BuyToken(msg.sender, _issued);
}

modifier onlyValidAddress(address _to){
require(_to != address(0x00));
_;
}

modifier onlyValidValue(address _from,uint256 _value){
require(_value <= balances[_from]);
_;
}

function balanceOf(address _owner)
view public
returns(uint256){
return balances[_owner];
}

function transfer(address _to, uint256 _value)
onlyValidAddress(_to) onlyValidValue(msg.sender,_value)
external
returns(bool){
balances[msg.sender] -= _value;
balances[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}

}

Decentralized exchange

Bây giờ mình sẽ viết một smart contract đóng vai trò như một Decentralized Exchange, điều mình mong muốn là có thể rao bán AmazingToken mà mình đang sở hữu nếu có ai đó chấp nhận mua nó bằng Ethereum.

pragma solidity >=0.5.1 <=0.6.0;contract AmazingTokenInterface{
uint256 public totalSupply;
function () external payable;
function balanceOf(address _owner) view public returns(uint256);
function transfer(address _to, uint256 _value)
public returns(bool);
}
contract AmazingDex {AmazingTokenInterface AmazingToken;address payable owner;

//1 ETH = 50 Token
uint256 public rate = 50;
modifier onlyValidAddress(address _to){
require(_to != address(0x00));
_;
}

modifier onlyOwner {
require(msg.sender == owner, "");
_;
}

constructor(address payable _amazingTokenAddress)
public {
AmazingToken = AmazingTokenInterface(_amazingTokenAddress);
owner = msg.sender;
}

function setRate(uint256 _rate)
onlyOwner
public
returns(uint256){
rate = _rate;
return rate;
}
function buyToken()
onlyValidAddress(msg.sender) public payable {
uint256 _value = (msg.value/10**18)*rate;
assert(AmazingToken.transfer(msg.sender, _value));
owner.transfer(msg.value);
}
}

Smart contract này thực hiện một điều rất đơn giản, khi có bất kỳ ai gọi method buyToken() thì mình sẽ trả token cho họ theo như rate đã được mình cung cấp, còn Ethereum sẽ ngay lập tức về ví của mình (trong AmazingDex chính là owner, người tạo ra AmazingDex).

Tất nhiên trước đó mình cần transfer AmazingToken sang cho AmazingDex contract này nắm giữ.

Kết bài

Bạn thấy đấy mọi thứ không tới nỗi khó như cách người ta muốn chúng ta tin như vậy. Nếu có bất cứ thảo luận, yêu cầu nào cho phần 3 hãy comment vào bên dưới.

--

--