蓋索林 Gasolin

如何撰寫智能合約(Smart Contract)?(III)建立標準代幣

上一篇中我們已寫好並部署完成了簡單的加密代幣🔒💵合約。在閱讀完本文後,你將學會建立一個可以放到乙太幣錢包👛的加密代幣🔒💵。

開發前的準備

延續上一篇的內容,在開發的過程中,我們將繼續使用testrpc1工具在電腦上模擬智能合約所需的乙太坊區塊鏈測試環境。

首先確保已啟動testrpc。若尚未啟動,可以使用以下命令啟動:

$ testrpc
...

建立的代幣若要能透過乙太幣錢包👛來收送,必須相容於以太坊的ERC20標準2。ERC20標準定義了支援錢包所必須的合約介面。

本篇將使用OpenZeppelin2函式庫來簡化建立加密代幣🔒💵的過程。OpenZeppelin是一套協助撰寫安全的加密合約的函式庫,裡面也提供了相容ERC20標準的智能合約。可以透過npm工具安裝到專案目錄node_modules/zeppelin-solodity/中:

$ npm install zeppelin-solidity

準備完成,我們可以開始建立新的加密代幣智能合約了。

建立一個標準代幣合約

contracts/目錄下建立一個HelloToken.sol檔案。也可以使用以下命令來產生檔案:

$ truffle create contract HelloToken

HelloToken.sol檔案內容如下:

pragma solidity ^0.4.11;
import "zeppelin-solidity/contracts/token/StandardToken.sol";
contract HelloToken is StandardToken {
string public name = "HelloCoin";
string public symbol = "[email protected]";
uint8 public decimals = 2;
uint256 public INITIAL_SUPPLY = 88888;
function HelloToken() {
totalSupply = INITIAL_SUPPLY;
balances[msg.sender] = INITIAL_SUPPLY;
}
}

講解

pragma solidity ^0.4.11;

第一行指名目前使用的solidity版本,不同版本的solidity可能會編譯出不同的bytecode。

import "zeppelin-solidity/contracts/token/StandardToken.sol";

接著我們使用import語句,來讀入zeppelin-solidity提供的StandardToken合約。

contract HelloToken is StandardToken {
}

建立HelloToken合約時,使用is語句繼承了StandardToken合約。因此HelloToken合約繼承了StandardToken合約所包含的資料與呼叫方法。

當我們繼承了StandardToken合約,也就支援了以下 ERC-20 標準中2規定的函式

函式 描述
totalSupply() 代幣發行的總量
balanceOf(A) 查詢A帳戶下的代幣數目
transfer(A,x) 傳送x個代幣到A帳戶
transferFrom(A,x) 從A帳戶提取x個代幣
approve(A,x) 同意A帳戶從我的帳戶中提取代幣
allowance(A,B) 查詢B帳戶可以從A帳戶提取多少代幣

和前一篇一樣,後面驗證時會用到balanceOftransfer兩個函式。因為StandardToken合約中已經幫我們實做了這些函式,因此我們不需要自己從頭再寫一次。

string public name = "HelloCoin";
string public symbol = "[email protected]";
uint8 public decimals = 2;
uint256 public INITIAL_SUPPLY = 88888888;

這邊設定參數的目的是指定這個代幣的一些特性。以美元為例,美元的名稱(name)是dollar,美元的代號為$,拿一美元去找零最小可以拿到零錢是一美分(cent),也就是0.01元。因為一美元最小可分割到小數點後2位(0.01),因此最小交易單位(decimals)為2。

這邊將這個加密代幣取名(name)為HelloCoin(哈囉幣),代幣的代號(symbol)為[email protected],最小分割單位是2(最小可以找0.01個哈囉幣)。

以下為美金,比特幣,以太幣,哈囉幣的對照表供參考:

Name Symbol decimals
Dollar $ 2
Bitcoin BTC 8
Ethereum ETH 18
HelloCoin [email protected] 2

最後也定義了初始代幣數目INITIAL_SUPPLY。這邊一樣選擇了一個吉祥數字88888888。 另外,當我們把全域變數設為public(公開),編譯時就會自動新增一個讀取公開變數的ABI接口,我們在truffle console中也可以讀取這些變數。

function HelloToken() {
totalSupply = INITIAL_SUPPLY;
balances[msg.sender] = INITIAL_SUPPLY;
}

和合約同名的HelloToken方法,就是HelloToken合約的建構函式(constructor)。 在建構式裡指定了totalSupply數目,並將所有的初始代幣INITIAL_SUPPLY都指定給msg.sender帳號,也就是用來部署這個合約的帳號。‵totalSupply定義於ERC20Basic.sol中,balances定義於BasicToken.sol中。

import '../math/SafeMath.sol';
...
using SafeMath for uint256;
...
balances[msg.sender] = balances[msg.sender].sub(_value);

進一步追去看BasicToken.sol合約的內容,可以發現BasicToken.sol合約中匯入了SafeMath.sol合約7SafeMath對各種數值運算加入了必要的驗證,讓合約中的數字計算更安全。

如此一來,我們已寫好一個可透過以太幣錢包交易的新加密代幣🔒💵合約。這個合約一經部署,就可以一直存在於以太坊區塊鏈上,世界上從此也就多了一種新的加密代幣。只要你能找到人想擁有這種代幣,這種代幣就有交易的價值。

編譯與部署

migrations/目錄下建立一個4_deploy_hellotoken.js檔案,內容如下:

var HelloToken = artifacts.require("HelloToken");
module.exports = function(deployer) {
deployer.deploy(HelloToken);
};

現在執行compile與migrate命令

$ truffle compile
...
$ truffle migrate --reset
Using network 'development'.
Running migration: 4_deploy_hellotoken.js
Deploying HelloToken...
... 0x2c4659528c68b4e43d1edff6c989fba05e8e7e56cc4085d408426d339b4e9ba4
HelloToken: 0x352fa9aa18106f269d944935503afe57a00a9a0d
Saving successful migration to network...
... 0x1434c1b61e9719f410fc6090ce37c0ec141a1738aba278dd320738e4a5d229fa
Saving artifacts...

如此一來我們已將HelloCoin代幣合約部署到testrpc上。

驗證

$ truffle console
> let contract
> HelloToken.deployed().then(instance => contract = instance)
> contract.address
'0x352fa9aa18106f269d944935503afe57a00a9a0d'
> contract.balanceOf(web3.eth.coinbase)
{ [String: '88888888'] s: 1, e: 4, c: [ 88888888 ] }
> contract.balanceOf(web3.eth.accounts[1])
{ [String: '0'] s: 1, e: 0, c: [ 0 ] }
> contract.transfer(web3.eth.accounts[1], 123)
...
> contract.balanceOf(web3.eth.accounts[0])
{ [String: '88888765'] s: 1, e: 4, c: [ 88888765 ] }
> contract.balanceOf(web3.eth.accounts[1])
{ [String: '123'] s: 1, e: 2, c: [ 123 ] }
>

講解

> let contract
> SimpleToken.deployed().then(instance => contract = instance)

這邊使用HelloToken.deployed().then語句來取得HelloToken合約的Instance(實例),並存到contract變數中,以方便後續的呼叫。

> contract.balanceOf(web3.eth.coinbase)
{ [String: '88888888'] s: 1, e: 4, c: [ 88888888 ] }
> contract.balanceOf(web3.eth.accounts[1])
{ [String: '0'] s: 1, e: 0, c: [ 0 ] }

web3.eth.coinbase 代表操作者預設的帳號,即testrpc所提供的10個帳號中的第一個帳號,也可以透過web3.eth.accounts[0]取得。 這兩句的目的是在進行轉帳操作前,先查詢前兩個帳號所擁有的代幣餘額。透過呼叫balanceOf函式,可以看到第一個帳號(部署合約的帳號)裡存著所有的代幣。

> contract.transfer(web3.eth.accounts[1], 123)
...

接著使用transfer函式來傳送123個代幣到第二個帳號web3.eth.accounts[1]。如果轉帳成功,傳送者預設帳號中會減少123個代幣,接收者帳號中會增加123個代幣。

> contract.balanceOf(web3.eth.coinbase)
{ [String: '88888765'] s: 1, e: 4, c: [ 88888765 ] }
> contract.balanceOf.call(web3.eth.accounts[1])
{ [String: '123'] s: 1, e: 2, c: [ 123 ] }
>

我們再次透過呼叫balanceOf函式,查詢傳送者帳號和接收者帳號各自剩下的HelloToken數目。發現轉帳真的成功了。

結語

我們用到 OpenZeppelin提供的函式庫來簡化撰寫加密代幣合約的工作。要實際投入使用前,還是建議將呼叫到的程式碼都再審查幾遍。可以從 OpenZeppelin 自己提供的Audit開始看4,可以學到一些觀念。

參考資料