Skip to main content

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

· 10 min read

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

開發前的準備

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

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

$ testrpc
...

這邊有個值得一學的小技巧:在啟動 testrpc 時加上--seed參數,例如

testrpc --seed apple banana cherry

這樣之後重新啟動 testrpc 時可以產生一樣的帳號 (accounts) 和私鑰 (private key)。

ERC20 標準

建立的代幣若要能透過乙太幣錢包👛 來收送,必須相容於以太坊的 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 = "H@";
uint8 public decimals = 2;
uint256 public INITIAL_SUPPLY = 88888;

function HelloToken() public {
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 = "H@";
uint8 public decimals = 2;
uint256 public INITIAL_SUPPLY = 100000;

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

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

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

NameSymboldecimals
Dollar$2
BitcoinBTC8
EthereumETH18
HelloCoinH@2

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

function HelloToken() public {
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來驗證HelloToken是否部署成功。

$ truffle console
> let contract
> HelloToken.deployed().then(instance => contract = instance)
> contract.address
'0x352fa9aa18106f269d944935503afe57a00a9a0d'
> contract.balanceOf(web3.eth.coinbase)
BigNumber { s: 1, e: 5, c: [ 100000 ] }
> contract.balanceOf(web3.eth.accounts[1])
BigNumber { s: 1, e: 0, c: [ 0 ] }
> contract.transfer(web3.eth.accounts[1], 123)
...
> contract.balanceOf(web3.eth.accounts[0])
BigNumber { s: 1, e: 4, c: [ 99877 ] }
> contract.balanceOf(web3.eth.accounts[1])
BigNumber { 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)
BigNumber { s: 1, e: 5, c: [ 100000 ] }
> contract.balanceOf(web3.eth.accounts[1])
BigNumber { 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.accounts[0])
BigNumber { s: 1, e: 4, c: [ 99877 ] }
> contract.balanceOf(web3.eth.accounts[1])
BigNumber { s: 1, e: 2, c: [ 123 ] }
>

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

結語

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

參考資料