Blockchain
Ethereum
solidity
truffle
ERC20

ERC20トークンをEthereumのプライベートチェーンに構築する パート①

対象者

ERC20規格のトークンを作りたい人

必要なもの

  • Node.js
  • npm
  • geth (※パート①では必要ありません)

ERC20とは

簡潔にいうとトークンのフォーマット。
分散型取引所やウォレットでのERC20規格のトークン同士の送金をシンプルにできる。

下記の6つのfunctionと2つのeventを実装できればERC20規格トークンの出来上がりです。

contract ERC20Interface {
    function totalSupply() public constant returns (uint);
    function balanceOf(address tokenOwner) public constant returns (uint balance);
    function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
    function transfer(address to, uint tokens) public returns (bool success);
    function approve(address spender, uint tokens) public returns (bool success);
    function transferFrom(address from, address to, uint tokens) public returns (bool success);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

パート①でできるようになること

ERC20規格の単純な送金処理の搭載したトークンを作成できるようになります。
現在(2018-02-03)、ICO(Initial Coin Offering)等に利用されているトークンもERC20規格の一部のみを実装したものが比較的多いです。

※ICOがわからない方はこれを機会に調べてみましょう!

開発環境の準備

今回はスマートコントラクト開発フレームワークであるtruffleを利用します。
まずはインストールしてみましょう。

console
$ npm install -g truffle

truffleがインストールされたか確認する。

console
$ truffle --version
Truffle v4.0.5 - a development framework for Ethereum
(省略)

ワークスペースの作成

truffleでは開発をスムーズに行うためのワークスペース(テンプレート)的なものを作成します。

console
$ mkdir ERC20-tutorial
$ cd ERC20-tutorial
$ truffle init
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!

Commands:

  Compile:        truffle compile
  Migrate:        truffle migrate
  Test contracts: truffle test

ワークスペース作成に成功していれば、下記のようなフォルダ構成となっているはず。

.
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── test
├── truffle-config.js
└── truffle.js

truffleでは、
contractsフォルダにsolidityのファイルを置きます。
migrationsフォルダのjsファイルでデプロイするsolidityファイルを選択します。

スマートコントラクトの実装

まずはcontractsフォルダにMyToken.solを作成する。
ERC20規格一部のfunctioneventを中身空で作成する。

MyToken.sol
pragma solidity^0.4.18;

contract ERC20Interface {

    // トークンの総発行量を返す
    function totalSupply() public constant returns (uint);

    // トークンの所有数を返す
    function balanceOf(address tokenOwner) public constant returns (uint balance);

    // トークンを送信する
    function transfer(address to, uint tokens) public returns (bool success);

    // トークン送信時、このイベントを発火させる
    event Transfer(address indexed from, address indexed to, uint tokens);

}

MyTokenのコントラクトを作成し、先ほど定義したERC20規格のコントラクトを継承させる。

MyToken.sol
pragma solidity^0.4.18;

contract ERC20Interface {
    (省略)
}

// is で 別のコントラクトを継承し利用できるようになる
contract MyToken is ERC20Interface {

    string public symbol;
    string public  name;
    uint8 public decimals;
    uint public _totalSupply;

    mapping(address => uint) balances;

    function MyToken() public {
        symbol = "MTK";
        name = "MYTOKEN";
        decimals = 18;
        _totalSupply = 1000000 * 10**uint(decimals);

        balances[msg.sender] = _totalSupply;

        Transfer(address(0), msg.sender, _totalSupply);

    }

    function totalSupply() public constant returns (uint) {
        return _totalSupply - balances[address(0)];
    }

    function balanceOf(address owner) public constant returns (uint balance){
        return balances[owner];
    }

    function transfer(address to, uint value) public returns (bool success) {
        require(balances[msg.sender] >= value && value > 0);

        balances[msg.sender] = balances[msg.sender] - value;
        balances[to] = balances[to] + value;

        Transfer(msg.sender, to, value);

        return true;

    }
}

はじめにトークンの情報を定義する。

string public symbol;  // トークンの略称
string public  name;  // トークンの名前
uint8 public decimals;  // トークンの分割可能な小数点
uint public _totalSupply;  // トークンの総量

mapping(address => uint) balances; // アドレスとトークン所有数の連想配列的なもの

コントラクトをブロックチェーンにデプロイ時、1度だけ呼び出されるfunctionを作成する。
contract名と同じ名前のfunctionでなくてはいけない。

function MyToken() public {

    // トークンの基本情報をセット
    symbol = "MTK";
    name = "MYTOKEN";
    decimals = 18;
    _totalSupply = 1000000 * 10**uint(decimals);

    // デプロイ者のアドレスにトークンを付与
    balances[msg.sender] = _totalSupply;

    // トークン送金は大事なイベントなので発火
    Transfer(address(0), mss.sender, _totalSupply);
}

トークン総発行量を取得するfunctionを作成する。

function totalSupply() public constant returns (uint) {
    return _totalSupply - balances[address(0)];
}

トークン所有量をアドレスから取得できるfunctionを作成する。

function balanceOf(address owner) public constant returns (uint balance){
    return balances[owner];
}

トークンを送金するfunctionを作成する。

function transfer(address to, uint value) public returns (bool success) {

    // 送金するトークン量が0より大きい、そして所有量を超えていないかチェック
    // requireは条件式を満たさない場合、falseを返す
    require(balances[msg.sender] >= value && value > 0);    

    // 送金元、送金先のトークン所有量を変更
    balances[msg.sender] = balances[msg.sender] - value;
    balances[to] = balances[to] + value;

    // 送金イベントを発火
    Transfer(msg.sender, to, value);

    return true;

}

solidityで書かれたコードに間違いがないか確かめるためコンパイルしてみましょう。
truffleにコンパイルする機能があるので利用します。

console
$ truffle compile
Compiling ./contracts/Migrations.sol...
Compiling ./contracts/MyToken.sol...
Writing artifacts to ./build/contracts

コンパイルが成功すると、buildフォルダが作成され中にjsonファイルが作成されます。
※失敗してしまう場合はsolidityのコードを見なおして見ましょう。

プライベートチェーンにデプロイ

スマートコントラクトのコードが完成したので、次はデプロイです。

truffleでは、自動でプライベートネットワークを立ち上げスマートコントラクトをデプロイしてくれる機能があります。そのためにはどのsolidityファイルをデプロイするのか指定してあげないといけません。

migrationsフォルダに2_deploy_contracts.jsというファイルを作成してください。
そして、solidityファイルを指定して上げましょう。
※ここは定型のコードなので深く考えずコピペで構いません。

2_deploy_contracts.js
// 拡張子をとったsolidityファイル名を指定
var MyToken = artifacts.require('MyToken');

module.exports = function(deployer) {
    deployer.deploy(MyToken);
}

まずはプライベートチェーンを立ち上げよう。

console
$ truffle develop
Truffle Develop started at http://localhost:9545/

Accounts:
(0) 0x627306090abab3a6e1400e9345bc60c78a8bef57
(1) 0xf17f52151ebef6c7334fad080c5704d77216b732

(省略)

$ truffle(develop)>

↑ truffle developはデプロイに成功すると10のアドレスが作成され、対話モード(コンソールに打ち込める状態)となります。
※デプロイに失敗する場合はsolidityファイルもしくは2_develop_contracts.jsに問題があります。

プライベートチェーンが立ち上がったので、スマートコントラクトをデプロイしよう。

console
$ truffle(develop)> migrate
Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x1c7ff1d4576905c5721c871a32b79ed8d337252dbc60fd5a2265770195ca76f1
  Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0
Saving successful migration to network...
  ... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying MyToken...
  ... 0xd6771d20fa80df5d224552c6a5aad2afcbc914ed38e53886f55a19825555b91f
  MyToken: 0x345ca3e014aaf5dca488057592ee47305d9b3e10
Saving successful migration to network...
  ... 0xf36163615f41ef7ed8f4a8f192149a0bf633fe1a2398ce001bf44c43dc7bdda0
Saving artifacts...

↑ MyToken: 0x345ca3e014aaf5dca488057592ee47305d9b3e10のアドレスにトークンがデプロイされたことが確認できます。

トークンの確認

ここからは対話モードで操作していきます。

まず、アカウントを表示させてみよう。

console
$ truffle(develop)> web3.eth.accounts
[ '0x627306090abab3a6e1400e9345bc60c78a8bef57',
  '0xf17f52151ebef6c7334fad080c5704d77216b732',
(省略)

次に、etherの残高を確認しよう。
※ehterはethereumのスマートコントラクトでの処理等で手数料として必要なトークンです。etherを消費することでトークンの送金を行うことができます。

console
$ truffle(develop)> web3.eth.getBalance(web3.eth.accounts[0])
{ [String: '99901350500000000000'] s: 1, e: 19, c: [ 999013, 50500000000000 ] }


$ truffle(develop)> web3.eth.getBalance(web3.eth.accounts[1])
{ [String: '100000000000000000000'] s: 1, e: 20, c: [ 1000000 ] }

↑ account[0]が少ないのはコントラクトをデプロイしたアドレスのため手数料分引かれているのが原因です。

トークンを操作するため、トークンのコントラクトオブジェクトをMTKに代入しよう。
ついでにアドレスも変数に代入してしまいます。

console
$ truffle(develop)> MTK = MyToken.at(MyToken.address)
(省略)


$ truffle(develop)> addr0 = web3.eth.accounts[0]
(省略)


$ truffle(develop)> addr1 = web3.eth.accounts[1]
(省略)

↑ MTKを利用して簡単に操作できるようになります。

トークンの情報を表示させよう。

console
$ truffle(develop)> MTK.name()
'MYTOKEN'

$ truffle(develop)> MTK.symbol()
'MTK'

$ truffle(develop)> MTK.totalSupply()
{ [String: '1e+24'] s: 1, e: 24, c: [ 10000000000 ] }

↑ トークンの名前は発行数を確認できました。

トークンの残高を確認してみよう。

console
$ truffle(develop)> MTK.balanceOf(addr0)
{ [String: '1e+24'] s: 1, e: 24, c: [ 10000000000 ] }

$ truffle(develop)> MTK.balanceOf(addr1)
{ [String: '0'] s: 1, e: 0, c: [ 0 ] }

↑ addr0がコントラクトをデプロイしたアドレスのためはじめにトークンを付与されています。

では、トークンを送金してみましょう。

console
$ truffle(develop)> MTK.transfer(addr1, 500)
{ tx: '0x8136e224f24243a581c94ea6482e3c81cc4f1f5743dfa1a76d3c2eca709aab59',
  receipt: 
   { transactionHash: '0x8136e224f24243a581c94ea6482e3c81cc4f1f5743dfa1a76d3c2eca709aab59',
     transactionIndex: 0,
     blockHash: '0xed6211c1d76cfd72d6032860d77469c86320c6e4ad78b2da179d0281f3d9337e',
     blockNumber: 5,
     gasUsed: 51323,
     cumulativeGasUsed: 51323,
     contractAddress: null,
     logs: [ [Object] ],
     status: 1 },
(省略)

↑ addr0からaddr1500MTK送金されました。
※本来はマイニングを行いトランザクションの承認を行わないと送金は完了しないのですが、truffle developでは自動でマイニングされトランザクションは承認されます。

では、トークンの残高を確認してみましょう。

console
$ truffle(develop)> MTK.balanceOf(addr0)
{ [String: '9.999999999999999999995e+23'] s: 1, e: 23, c: [ 9999999999, 99999999999500 ] }

$ truffle(develop)> MTK.balanceOf(addr1)
{ [String: '500'] s: 1, e: 2, c: [ 500 ] }

↑ aadr0からaddr1500MTK送金できているのが確認できましたね。

以上、送金を行えるトークンを作成することができました。
パート②ではパート①では実装していない、自分以外のアドレスからの送金を実装します。

今回qittaで記事を書くことが初めて、またスマートコントラクトの知識の豊富ではないため多々間違いがあるかもしれません。もし意見や間違い等ありましたらコメントください。

参考

https://theethereum.wiki/w/index.php/ERC20_Token_Standard