Ethereum開発チュートリアル – GethからTruffleでERC20トークン発行まで
# 目次
- 目次
- 事前準備
- EthereumクライアントGo Ethereumを触ってみる
- JSON RPCでノードを操作する
- Infura.ioを使ってノードにアクセスする
- Ethereum JavaScript APIを使ってノードへアクセス
- 通貨(Ether)のやり取りをする
- Solidityとコントラクトの公開
- トークンを発行する
- その先へ
こんにちは、シュンです.
この記事はEthereum開発未経験向けのチュートリアルです.
環境構築からオリジナルトークン発行までを網羅しています.
geth、Truffle、Infura.io、Zeppelin Solidityなどの周辺ツールの使い方も習得できます.
# 事前準備
このチュートリアルではローカル作業はすべて仮想マシンのUbuntu上で行います.
Ubuntu17.10を使用します.
まずはその環境を構築していきましょう.
# なぜ仮想マシンUbuntuの上で行うか
仮想マシンUbuntuで行う理由は2つあります.
1つ目は、手がつけられていないまっさらな環境を用意するためです.
普段使いのPCにはこれまでの様々な設定、ファイル、フォルダなどが含まれてます.
それらが原因で、書いてある通りに実行してもエラーが起きることはよくあります.
2つ目は、問題があった時のサポートを受けやすくするためです.
このチュートリアルで分からないことがあればこの記事に直接コメントで質問して下さい.
オレも全く同じ環境をすぐに作れるのでサポートがしやすいです.
また、オープンソースの世界では一般にLinux系OSの方が情報が見つかりやすいです.
今後も何か新しいことを学ぶ際は、専用の環境を用意しましょう.
# VagrantとVirtualboxのインストール
それぞれ以下からダウンロードとインストールをしてください.
もしVirtualBoxだけを普段から使っている方は、
Vagrant無しでbento/ubuntu-17.10
のイメージをインストールしてログインするのでもOKです.
Vagrant
https://www.vagrantup.com/downloads.html
VirtualBox
https://www.virtualbox.org/wiki/Downloads
インストール後はPCを再起動しましょう.
Macの場合はターミナルを、Windowsの場合はコマンドプロンプトを開きます.
以下のコマンドを実行して、2.0.1
などバージョンが表示されたらOKです.
$ vagrant -v
なお、ここでは$
はMacのターミナル上で入力されたコマンドを表します.
コピペする際は、$
を除いてください.
# 今回のプロジェクトを作成
プロジェクト名はhello_ethereum
としましょう.
$ mkdir hello_ethereum
$ cd hello_ethereum
$ vagrant init bento/ubuntu-17.10
# Ubuntuを立ち上げてログインする
立ち上げます.
このコマンドは数分かかる場合があります.
$ vagrant up
$ vagrant ssh
以下のメッセージが出れば仮想マシンUbuntuへのログイン成功です.
Welcome to Ubuntu 17.10 (GNU/Linux 4.13.0-21-generic x86_64)
exit
コマンドかCtrl+D
でログアウトして、Mac/Windowsに戻ることができます.
# EthereumクライアントGo Ethereumを触ってみる
# Go Ethereumとは?
GO言語で実装された公式Ethereumクライアントです.
コマンドはgeth(ゲス)です.
他の言語でも、C++やPythonなどで実装されたものもあります.
GO言語で実装されているといってもGO言語が出てくることはありません.
知っている必要はありません.
gethでは以下のことが出来ます.
- EthereumのP2Pノードの立ち上げ
- プライベートネットワーク立ち上げ
- 開発用、本番用(Main)ネットワークへの参加
- JavascriptのREPLを使ったインタラクション
- Ethereumアカウント作成
- マイニング
- コントラクト公開
などなど.
REPL
Read-eval-print loopの略
字のごとく読んで評価して表示して繰り返す.対話型評価環境を指す。ただし、インタープリタと同義ではない。
# gethのインストール
ここからは基本的にUbuntu上での作業になります.
vagrant@vagrant:~$
はUbuntu上で入力したコマンドを表しています.
vagrant@vagrant:~$ sudo add-apt-repository -y ppa:ethereum/ethereum
vagrant@vagrant:~$ sudo apt-get update
vagrant@vagrant:~$ sudo apt-get install -y ethereum
# プライベートネットワークを立ち上げる
作業用ディレクトリを作ります.
その中にさらにデータ用ディレクトリを作ります.
ここにはgeth起動後、ブロックチェーンのデータなどが蓄積されていきます.
vagrant@vagrant:~$ mkdir eth_private_network
vagrant@vagrant:~$ cd eth_private_network
vagrant@vagrant:~$ mkdir data
立ち上げます.同時にJavascriptのREPLコンソールも立ち上げます。
# 最初のブロックを定義
ジェネシスブロックと呼ばれるチェインの最初に当たるブロックを定義します.
json設定ファイルを使います.
実はこれは使わなくてもデフォルトで設定されます.
とはいえ、自分で設定したほうが分かりやすいうえ、他のノードを接続する時に楽です.
以下のファイルgenesis.jsonをダウンロードしてください.
vagrant@vagrant:~/eth_private_network$ wget https://gist.githubusercontent.com/shunsukehondo/ec84c1a26be25b4c55796c830421e29c/raw/f4d8d9f683eb4b8ef5070bdd4c290a9e923b97e4/genesis.json
# ブロックチェーンの初期化
genesis.jsonファイルを使って初期化します.
vagrant@vagrant:~/eth_private_network$ geth --datadir ./data init genesis.json
もしもエラーが出る場合はすでにブロックチェーンが初期化済みの可能性があります.
以下のコマンドで削除してからもう一度実行してみて下さい.
vagrant@vagrant:~/eth_private_network$ rm -rf ./data/*
# gethの起動
gethを起動します.
vagrant@vagrant:~eth_private_network $ geth --networkid 777 --datadir ~/data --nodiscover console 2>> ./geth_err.log
パラメータの説明.
--networkid
どのネットワークに参加するか.0=メイン、1~3=開発、それ以外=プライベート。--datadir
ブロックチェーンのデータなどの格納先ディレクトリ.--nodiscover
他人のノードが勝手に接続出来ないようにする.console
Javascript REPLコンソールを立ち上げ.2>> geth_err.log
gethのパラメータではないです.geth_err.logにログを出力。
# gethを触ってみる
以下>
はgethのJavascriptコンソールであることを表します.
# アカウントの作成
新規アカウントを作成します.
"hoge"
は任意のパスワードです.
度々入力する機会があります.
> personal.newAccount("hoge")
"0x28995a352ffd60a9c5d80fdc8af5e3d50eb9d234"
# アカウントの確認
現在作成されているアカウントが配列形式で返されます.
> eth.accounts
["0x28995a352ffd60a9c5d80fdc8af5e3d50eb9d234"]
# マイニング
マイニング用のアカウントを確認します.
> eth.coinbase
"0x28995a352ffd60a9c5d80fdc8af5e3d50eb9d234"
先ほど作成したアカウントがセットされています.
以下のようにして明示的にセットすることもできます.
> miner.setEtherbase("0x28995a352ffd60a9c5d80fdc8af5e3d50eb9d234")
マイニングを開始します.
> miner.start()
null
マイニングが行われているか確認します.
> eth.mining
true
ブロックが増えているか確認してみましょう.
> eth.blockNumber
0なら増えていません.
マシンスペックなどにより時間が経っても増えないこともあります.
次にマシンスペックに依存しない方法へ移行しますので、
あきらめて次に行きましょう.
# JSON RPCでノードを操作する
Ethereum上のアプリは、
- ネットワークに参加しているノード
- JSON RPCでノードを操作するフロントエンド
の2つが必要になります.
RPC
リモートプロシージャコールとは、ネットワークによって接続された他のコンピュータ上でプログラムを呼び出し、実行させるための手法のことである.あるいは、そのためのプロトコルのことである。
# gethでRPCを有効にする
有効にするために必要なのは、パラメータを少し増やすことだけです.
まず、Ctrl-C
などでgethを一度停止します.
vagrant@vagrant:~/eth_private_network$ geth --networkid 777 --datadir ./data --nodiscover --rpc --rpcaddr "localhost" --rpcport "8545" --rpcapi "admin,eth,miner,net,personal,web3" console 2>> ./geth_err.log
--rpc
HTTPでRPCを有効化--rpcaddr
RPCのアドレス--rpcport
RPCのポート番号--rpcapi
RPCで実行を許可するコマンド
# CURLでリクエストを送ってみる
gethを起動したままで、ターミナル or コマンドプロンプトで新規タブを開いて下さい.
そのタブでUbuntuにログインしていない場合は、ログインして下さい.
curl
コマンドを使って、JSONをポストします.
以下を実行してみて下さい.
vagrant@vagrant:~$ curl -H 'Content-Type:application/json' -s -X POST --data '{"jsonrpc":"2.0","id":67867379,"method":"eth_blockNumber","params":[]}' http://localhost:8545
以下のようなレスポンスが返ってきたら成功です.
{"jsonrpc":"2.0","id":67867379,"result":"0x0"}
"method"
と"params"
が重要なパラメータです.
ここを書き換えることで様々なリクエストを送ることが出来ます.
勘が良い方はお気づきかと思いますが、
"eth_blockNumber"
はgethのコンソールで先ほど実行したeth.blockNumber
と同じ役割です.
“method”の詳細とパラメータはこちらで一覧されています.
https://github.com/ethereum/wiki/wiki/JSON-RPC
# gethのパラメータを保存する
次に進む前にgethのパラメータを保存しておきましょう.
このチュートリアルではもうgethは使用しません.
各自で開発する際に役立ててください.
gethを停止します.
先ほどのgethのパラメータからconsole 2>> ./geth_err.log
部分を削除して、dumpconfig > geth_conf.toml
に置き換えて下さい.
vagrant@vagrant:~/eth_private_network$ geth --networkid 777 --datadir ./data --nodiscover --rpc --rpcaddr "localhost" --rpcport "8545" --rpcapi "admin,eth,miner,net,personal,web3" dumpconfig > geth_conf.toml
dumpconfig
がパラメータをファイルに出力するコマンドです.
これで次回以降、以下のコマンドでgethを起動できるようになります.
vagrant@vagrant:~/eth_private_network$ geth --config geth_conf.toml console 2>> ./geth_err.log
# Infura.ioを使ってノードにアクセスする
さて、ここまでは自分の仮想マシン上で作業を完結させてきました.
しかしEthereumのノードを立てるのはできれば自分のマシンでやりたくはありません.
CPUと容量を大きく消費するからです.
Infura.ioというEthereumのノードホスティングサービスがあります.
それを利用しましょう.
# Infura.ioとは
Infura.ioが管理しているEthereumのノードを利用できるサービスです.
本番用と開発用ノードが揃っています.
無料で利用できます.
# Infura.ioに登録する
こちらから登録して下さい.
メールが届きます.
そこにメインネット(本番)、開発用ネットのノードへ接続するためのURLが記載されています.
# Infura.io のノードにアクセスする
アクセスしてみましょう.
先ほどlocalhoslへCURLしたコマンドから、最後のURLだけ変えたものです.
XXXXXXX
の部分はあなたのキーに置き換えてください.
ropstenとは、3つある開発用ネットの1つです.
networkidは3です.
vagrant@vagrant:~$ curl -H 'Content-Type:application/json' -s -X POST --data '{"jsonrpc":"2.0","id":67867379,"method":"eth_blockNumber","params":[]}' https://ropsten.infura.io/XXXXXXX
こんなようなレスポンスが返ってきたらOKです.
{"jsonrpc":"2.0","id":67867379,"result":"0x2d5532"}
ローカルホストではないので、先ほどよりも時間がかかったと思います.
EthereumのJSON RPCでは数値が16進数で表されています.
返ってきた"result": "0x2d5532"
は、2970930
ということになります.
あなたが実行する時にはさらにブロック数が増えているはずです.
# Ethereum JavaScript APIを使ってノードへアクセス
web3.js – Ethereum JavaScript API
これまでは名前のJSONを書いてCURLでアクセスしてきました.
しかし、実際の開発ではそれらを使いやすくラップしたライブラリを使用します.
様々な言語での実装があります.
ここでは最も使われてるJavascriptのweb3.jsを使います.
# NodeJS, NPMのインストール
web3はJavascriptのライブラリです.
それをコンソールから使用するためにNodeJSとNPMをインストールしましょう.
vagrant@vagrant:~$ sudo apt-get -y update
vagrant@vagrant:~$ sudo apt-get -y install nodejs
vagrant@vagrant:~$ sudo apt-get -y install npm
# web3のインストール
web3作業用のディレクトリを作成します.
vagrant@vagrant:~$ mkdir hello_web3
vagrant@vagrant:~$ cd hello_web3
インストールします.
vagrant@vagrant:~/hello_web3$ npm install web3
# web3を使ってInfura.ioのノードへアクセスする
Infura.io用にweb3を初期化するスクリプトです.
ダウンロードします.
vagrant@vagrant:~/hello_web3$ wget https://gist.githubusercontent.com/shunsukehondo/42d87940d2f7388939ed747bcc5ee6fd/raw/d49a036b7a0d96fc85c16e528aa567ce9280cfb5/init.js
テキストエディタでinit.jsを開き、
XXXXXXX
の部分をあなたのキーに書き換えてください.
Nodeコンソールを起動します.
vagrant@vagrant:~/hello_web3$ node
> require("./init.js")
> web3.eth.getBlockNumber().then(console.log)
先ほどと同じような2971160
が表示されればOKです.
(先ほどより少し増えていますね)
# 通貨(Ether)のやり取りをする
# Ropsten用にアカウントを用意する
Infura.ioではノードのホスティングはしてくれます.
しかし、アカウントの作成は出来ません.
あなたの秘密鍵を預けてしまうことになりますからね.
ですので、何らかの方法で作成する必要があります.
ここでは先ほどプライベートネット用に作成したアカウントをそのまま使うことにしましょう.
gethでpersonal.newAccount("hoge")
を実行してアカウントを作成したのを覚えていますか?
data
というディレクトリにデータを格納するようにしたはずです.
~/eth_private_net/data/keystore
ディレクトリにアカウントの情報が入っています.
それをkeyObj
という名前でコピーして下さい.
vagrant@vagrant:~/hello_web3$ cp ../eth_private_network/data/keystore/UTC--2018-04-04T03-07-23.987289102Z--d62ac3afef372b7824b41e5e212dc833ca383364 ./keyObj
このファイルは、パスワードhoge
と一緒にアカウントを暗号化し守っているものです.
お好きなテキストエディタでkeyObj
を開いて下さい.
vagrant@vagrant:~/hello_web3$ vi keystore
先頭にkeyObj =
を追記して、以下のように編集して保存して下さい.
keyObj = {...}
読み込めるかどうかテストしてみましょう.
$ node
でnodeを起動して下さい.
以下を実行してみてkeyObj.address
の内容が表示されたらOKです.
> require("./keyObj")
> console.log("0x" + keyObj.address)
表示された0xXXXXXXXXXXXXXXXXXXXXXXXXX
をコピーしておいて下さい.
この後使用します.
# Etherを受け取る
Ethereumではネットワーク上のやり取りに通貨であるEtherが必要になります.
本番のmainnetでは
- Zaifなどの取引所で法定通貨と交換する
- マイニングする
- 誰かに送ってもらう
のいずれの方法を取る必要があります.
Ropstenなどの開発用ネットでは、Etherを送ってくれるサービスがあります.
今回はこれを利用します.
以下のページにアクセスして下さい.
先ほどコピーしたアドレスをSendToの右に貼り付けて下さい.
SendToをクリックしリクエストして下さい.
10秒ほどで確定します.
# 受け取ったEtherを確認する
nodeを起動して下さい.
以下を順番に実行して、0以上の数字が返ってきたらOKです.
その値があなたのアカウントに記録されているEtherです.
> require("./init.js")
> require("./keyObj")
> var address = "0x" + keyObj.address
> web3.eth.getBalance(address, "latest").then(function (res) {console.log(web3.utils.fromWei(res, "ether"))})
最後のコマンドでやっていることは3つです.
web3.eth.getBalance
でaddress
アカウントに記録されている金額をInfura.ioに問い合わせる- 単位がWeiなので、
web3.utils.fromWei
でEtherに変換する - コンソールに出力する
なぜInfura.ioに繋がるか、はinit.js
を読んで思い出して下さい.
Promise
.then
はプロミスと呼ばれる実装パターンです.web3.js ver.1によく出てきます。非同期処理が完了するとthen()
に引数が渡され実行されます。詳しくは「Javascript プロミス」で調べてみて下さい。
# 送金する
お金が手に入ったので送金してみましょう.
送金の場合は、アカウント所持者の許可なく実行されたら問題ですよね.
アカウントにあなたのサインが必要になります.
# 必要なライブラリのインストール
まずは送金のために便利なライブラリをインストールしましょう.
vagrant@vagrant:~/hello_web3$ npm install ethereumjs-tx
vagrant@vagrant:~/hello_web3$ npm install keythereum
# トランザクションの作成
送金トランザクションを作っていきます.
nodeを起動して下さい.
web3などの初期化とアドレスのキーオブジェクトを生成しておきます.
> require("./init.js")
> require("./keyObj")
以下のパラメータを作成していきます.
nonce
アカウントの出力トランザクションの数に等しい.もしPending中のトランザクションを持っている場合はそれも考慮する。gasPrice
トランザクションにかかるであろうコスト(Ether).これが高いほど早く採掘される。gasLimit
トランザクション処理に費やす最大gas.これを超えると中断され、費やした分はマイナーに送られる。(gas)to
送られるアカウントvalue
送られる金額(Ether)data
任意のメッセージ、または関数呼び出しやコントラクト作成コードなど.
# nonce
まずはnonce
はあなたのアカウントから送られたトランザクション数に等しいです.
Pending中のトランザクションも加えて下さい.
あなたの他のトランザクションとnonce
が重複した場合は、先に処理された方だけが正しいものになります.
以下のコードでセットできます.
> var address = "0x" + keyObj.address
> var nonce
> web3.eth.getTransactionCount(address).then(function(count) { nonce = count; })
# data
data
ですが今回は使用しません.
よって空文字列を入れます.
> var data = ""
# to
to
フィールドは宛先です.
0x
から始まる送金相手のアドレスを指定します.
今回はオレのアドレスに送ってみてください.
0x091225B0D977922de7483e65e24bb9d17dF687EC
> var toAddress = "0x091225B0D977922de7483e65e24bb9d17dF687EC"
# gasPrice
gasPriceはあなたのトランザクションをマイナーが処理した時にもらえる報酬の「時給」のようなものです.
報酬は、
時給 x 働いた時間
で決まります.
つまり、gasPrice
x あなたのトランザクションのマイニングにかかった労力です.
このgasPrice
が高ければ高いほど早くマイナーに採掘されやすくなります.
実際にトランザクションを
以下のコードで現在の平均gasPrice
を取得できます.
> web3.eth.getGasPrice().then(console.log)
今回はこれをそのまま使うことにしましょう.
> var gasPrice
> web3.eth.getGasPrice().then(function(price) { gasPrice = Number(price); })
# gasLimit
gasLimit
はあなたを守るために設定します.
予想外の事態で大量のEtherを消費してしまわないように制限をかけます.
後述の「コントラクト」で誤って無限ループが混入した場合などです.
コントラクト
Solidityにおける、オブジェクト指向言語のクラスに相当するもの.デプロイ時の1度限りインスタンス化される。Ethereum上での開発のゴールとは、つまるところコントラクトのインスタンス化である。
制限を越えたトランザクションは中断され、かかった分だけマイナーに支払われます.
web3.eth.estimateGas
というメソッドで、かかるgasの量をシミュレートできます.
引数には、送信先アドレスと送信データを渡します.
> web3.eth.estimateGas({to: "0x091225B0D977922de7483e65e24bb9d17dF687EC", data: ""}).then(console.log)
今回は21000
が返ってきました.
限界値なので、少し増やして25000
を採用しましょう.
> var gasLimit = 25000
以下のコマンドであなたのトランザクションにかかる最大費用が計算できます.
gasPrice
はWei単位であることに注意して下さい.
> web3.utils.fromWei((rawTx.gasPrice * rawTx.gasLimit).toString(), "ether")
今回は0.0005
Etherでした.
# value
value
に送金金額を指定します.
今回は0.1Ether送ってみましょう.
もちろん様々な金額でテストしてみて構いません.
> var value = Number(web3.utils.toWei("0.1", "ether"))
これで入力するフィールドは以上です!
# パラメータをまとめる
パラメータはすべて16進数に変換して1つのテーブルにまとめます.
web3.utils.numberToHex
というメソッドを使いますが、
タイピングが長いのでn2h
とエイリアスを貼っておきます.
> var n2h = web3.utils.numberToHex
>
> var rawTx = {}
> rawTx.nonce = n2h(nonce)
> rawTx.data = ""
> rawTx.to = toAddress
> rawTx.gasPrice = n2h(gasPrice)
> rawTx.gasLimit = n2h(gasLimit)
> rawTx.value = n2h(value)
これでデータは完成です.
# トランザクションへの署名
送金のためのトランザクションを発行するにはあなたの署名が必要です.
署名は鍵ファイルとパスワードから復元された「秘密鍵」によって可能です.
まずは必要なライブラリをインポートします.
> const keythereum = require("keythereum")
> const Tx = require("ethereumjs-tx")
hoge
をあなたのパスワードに置き換えて以下を実行して下さい.
> var password = "hoge"
> var privateKey = keythereum.recover(password, keyObj)
ライブラリのトランザクションオブジェクトにラップします.
そして署名をします.
> var tx = new Tx(rawTx)
> tx.sign(privateKey)
# トランザクションを送信する
ついにトランザクションを送信しましょう!
トランザクションデータを送信できる形にシリアライズして、送信するだけです.
> web3.eth.sendSignedTransaction("0x" + tx.serialize().toString("hex")).once("transactoinHash", function (hash) { console.log("TX hash: " + hash); }).once("error", function (err) { console.log("Error: " + err); })
TX hash: 0xf1fb315e0ed9fb2ae850bf26ab8a44c6623feed0bdc57495d972b366d18a2dbf
のようなトランザクションIDが表示されたら成功です.
# トランザクションを確認する
Etherscanというサービスを使用してトランザクションの状態をモニターすることができます.
0x
から始まる部分はあなたのトランザクションIDに置き換えてください.
https://ropsten.etherscan.io/tx/0xf1fb315e0ed9fb2ae850bf26ab8a44c6623feed0bdc57495d972b366d18a2dbf
オレの環境では、約5分ほどで取り込まれました.
# 消費したEtherを確認する
送金後の残高に送金額と手数料を足し合わせると送金前の残高に一致するはずです.
送金前 = 送金後 + 送金額 + 手数料
先ほどのEtherscanで、実際にかかった手数料が分かります.
Gas Used By Txn:
実際に消費されたgasGas Price
設定したgasの価格Actual Tx Cost/Fee:
上記をかけ合わせた手数料(Ether)
計算してみましょう.
オレの残高は送金前2
Etherでした.
送金額は0.1
Ether、手数料は0.000168
Etherです.
> var before = Number(web3.utils.toWei("2", "ether"))
> var after
> web3.eth.getBalance(address).then(function (balance) { after = balance; })
> var value = Number(web3.utils.toWei("0.1", "ether"))
> var fee = Number(web3.utils.toWei("0.000168", "ether"))
> before === after + value + fee
true
確かにtrue
が返ってきました.
# 送金トランザクション用スクリプト
以下に上記の内容をスクリプト化しています.
使い方はGistのコメントを参照して下さい.
# Solidityとコントラクトの公開
送金トランザクションお疲れ様でした.
次はSolidity言語を使って「コントラクト」を作っていきます.
Ethereumがワールドコンピュータたる所以ですね.
真骨頂です.
とはいえ、やることはそんなに難しくありません.
トランザクションが作れるようになったので簡単です.
これから何度かトランザクションの送信を行います.
トランザクションの作成と送信を関数にまとめたのでダウンロードしてください.
以下のコマンドでダウンロードして下さい.
wget https://gist.githubusercontent.com/shunsukehondo/e8ac1c1fe762bf5936c3b149daf76de5/raw/ba5327855aec66a2230938ddbdfde8ac9d928258/send_signed_transaction.js
以下の定数を設定して下さい.
PASSWORD
アカウント作成時に設定したパスワードに置き換えるkeyObj
行ごとrequire("./keyObj")
に置き換えるINFURA_KEY
Infura.ioのキーに置き換える
# コントラクトとは
Solidityのコントラクトとは、オブジェクト指向言語におけるクラスです.
状態変数(メンバ変数)と関数で構成されます.
多重継承もサポートされています.
オブジェクト指向言語のクラスはプログラム実行中何度もインスタンス化されます.
一方で、Solidityのコントラクトはデプロイ時の1度のみインスタンス化されます.
また、公開メソッドは世界中の誰でも呼び出すことが可能です.
だからこそちょっとしたミスで何億ものお金が盗まれるのです.
# コンパイラのインストール
Ethereumで現在最も広く使われている言語はSolidityです.
solcというコンパイラをインストールしましょう.
vagrant@vagrant:~/hello_web3$ sudo apt-get -y update
vagrant@vagrant:~/hello_web3$ sudo apt-get -y install solc
# Hello, World!!
以下はHelloWorldのサンプルです.
メソッドは修飾子に特徴があります.
pure
はブロックチェーンの状態を閲覧すらしないもの.
view
は閲覧のみ.
pure
view
をいずれも付けないと、書き換えも行うという意味です.
書き換えも伴う場合、メソッドの実行者に手数料が発生します.
# コンパイル
先ほどのファイルをHelloWorld.solcという名前で保存して下さい.
以下のコマンドを実行するとabi
とbin
ファイルが書き出されます.
vagrant@vagrant:~/hello_web3$ solc -o . --overwrite --bin --abi HelloWorld.sol
Javascriptからの使いやすさのためにabi、binファイルを少し編集しましょう.
abiファイルを開いて先頭にabi =
を加えて保存して下さい.
binファイルを開いて先頭にbin = "0x
を加え、末尾に"
を加え保存して下さい.
binファイルはこんな見た目になります.
bin = "0x6060604052341561000f57600080fd5b6101578061001e6000396000f300606060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636d4ce63c14610046575b600080fd5b341561005157600080fd5b6100596100d4565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561009957808201518184015260208101905061007e565b50505050905090810190601f1680156100c65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100dc610117565b6040805190810160405280600d81526020017f48656c6c6f20576f726c64212100000000000000000000000000000000000000815250905090565b6020604051908101604052806000815250905600a165627a7a723058203d397d558cc66a1951d8be3b6283b30d81a5d4de16e4bace3259aac74df6446f0029"
それではデプロイ(公開)してみましょう.
nodeを起動して下さい.
初期化、abi、binを読み込みます.
> require("./send_signed_transaction.js")
> require("./HelloWorld.abi")
> require("./HelloWorld.bin")
デプロイ用のデータを作成します.
> var contract = new web3.eth.Contract(abi)
> var deploy = contract.deploy({data: bin}).encodeABI();
基本的には以上です.
送金トランザクションを作成した時にdata
パラメータが空だったのを覚えていますか?
そこにこのdeploy
を入れて送るのです.
送金先は不要なのでto
パラメータは不要です.
送金額は``Etherです.
では送信しましょう.
> var address = "0x" + keyObj.address
> sendTransaction(deploy, 0, address, address)
コントラクトデプロイ時にはto
パラメータは意味を持ちません.
to
にもfrom
と同じ、自分のアドレスを指定しています.
先ほどの送金と同じく、成功するとトランザクションIDが表示されます.
Etherscanでマイニングを見届けましょう.
https://ropsten.etherscan.io/address/0xedb81b076dc8ac2b6701ce1fbca6f5e7af634eca
# コントラクトを呼び出す
ネットワークに一度デプロイされたコントラクトは、世界中のクライアントから呼び出し可能です.
これがEthereumがワールドコンピュータである所以です.
先ほどのHelloWorldを呼び出してみましょう.
呼び出しにはABIとコントラクトアドレスの2つが必要です.
コントラクトがデプロイされるとアドレスが付与されます.
ABIは手元にファイルがありますね.
アドレスは先ほどのデプロイトランザクションのContract Addressがそれにあたります.
この例では0xeDB81b076Dc8Ac2b6701ce1Fbca6f5E7Af634ECa
です.
nodeを起動して下さい.
初期化とabiをロードします.
> require("./init.js")
> require("HelloWorld.abi")
コントラクトを生成します.
> var HelloWorld = new web3.eth.Contract(abi, "0xeDB81b076Dc8Ac2b6701ce1Fbca6f5E7Af634ECa")
呼び出します.
> HelloWorld.methods.get().call().then(console.log)
Hello World!!
と表示されたらOKです.
ちなみにこのcall()
では、手数料がかからない関数のみを呼び出すことができます.
# データ更新を伴う関数実行
先ほどは手数料がかからない関数を実行しました.
次は手数料の発生する、データ更新を伴う関数実行をみていきましょう.
# コントラクトの定義
まずはSolidityで状態をもつコントラクトを定義しましょう.
以下をOwnerName.solという名前で保存して下さい.
コードについて解説します.
mapping (address => string) public ownerToName;
mapping
は型です.キーと値をマッピングした辞書です。
アドレスを渡すと文字列が返ってきます.
修飾子public
をつけることによってゲッターだけが自動で定義されます.
セッターは自分で定義する必要があります.
つまり、値を更新する関数は自分で作れということです.
function setName(string name) external
setterにあたる関数です.
修飾子external
をつけることによって、コントラクトの外部からのみ実行出来る関数になります.
ownerToName[msg.sender] = name;
辞書にアドレスをキーとして名前の文字列をセットしています.
msg.sender
というのは関数実行者のアドレスです.
Ethereumのコントラクト実行は、必ず誰かが関数を呼び出すところから始まります.
ですので、必ず値がセットされています.
# コントラクトのデプロイ
デプロイ手順は先ほどのHelloWorldと全く同一です.
コンパイルしてABI・BINを生成し、トランザクションに乗せて送信します.
念のため再掲します.
vagrant@vagrant:~/hello_web3$ solc -o . --overwrite --bin --abi OwnerName.sol
OwnerName.abi, OwnerName.binを直接編集してrequire
できる形式に変更して下さい.
> require("./init.js")
> require("./OwnerName.abi")
> require("./OwnerName.bin")
デプロイ用のデータを作成します.
> var contract = new web3.eth.Contract(abi)
> var deploy = contract.deploy({data: bin}).encodeABI()
送信します.
> var address = "0x" + keyObj.address
> sendTransaction(deploy, 0, address, address)
トランザクションIDからEtherscanでコントラクトのアドレスを調べておいて下さい.
次の関数呼出し時に使います.
# 関数の呼び出し
データの変更を伴う関数呼び出しは、
トランザクションを送信し、マイニングされる必要があります.
まずはデータ変更前の状態をみてみましょう.
> var address = "0x" + keyObj.address
> var contractAddr = "0x21d5d584FBd0401708DCDb63DcCE61d7723cEaCF"
> var OwnerName = new web3.eth.Contract(abi, contractAddr)
> OwnerName.methods.ownerToName.get(address).call().then(console.log)
何も表示されなければOKです.
ではデータ変更のトランザクションを送信しましょう.
> var data = OwnerName.methods.setName("Shun").encodeABI()
> sendTransaction(data, 0, address, contractAddr)
to
アドレスにはコントラクトのアドレスを指定することに注意してください.
デプロイと同様にトランザクションIDが発行され、数秒後にマイニングされます.
もう一度名前を取得してみましょう.
> OwnerName.methods.ownerToName.get(address).call().then(console.log)
"Shun"
と表示されたら成功です.
# トークンを発行する
トークンとはEtherumブロックチェーン上に記録された通貨です.
ユーザーアドレスごとに残高が記録してあり、送金が出来ます.
今回はERC20トークンというものを実装してみます.
トークン宛にEtherの送金があった場合に、1Ether=10Tokenの交換比率で自動で配られることにしましょう.
まずは作業ディレクトリを作りましょう.
vagrant@vagrant:~/hello_web3$ mkdir ~/hello_token
vagrant@vagrant:~/hello_web3$ cd ~/hello_token
# Truffleの導入
# Truffleとは
https://github.com/trufflesuite/truffle
Ethereum開発のための開発環境、テストツール、アセットパイプラインなどのツール群です.
デファクトスタンダードとなっています.
今回はSolidityライブラリ管理のために導入します.
# Truffleのインストール
> npm instlal truffle
Truffle用のディレクトリを作り、初期化しましょう.
vagrant@vagrant:~/hello_token$ mkdir truffle
vagrant@vagrant:~/hello_token$ cd truffle
vagrant@vagrant:~/hello_token/truffle$ ../node_modules/.bin/truffle init
# MetaMaskでウォレットを作成
Truffleでコンパイルからデプロイまで快適に行うためにMetaMaskでウォレットを作成しておきましょう.
MetaMaskはEthereumのウォレットを管理してくれるGoogle Chrome、Firefox拡張です.
実は多くのEthereumウェブアプリが裏でMetaMaskを使っており、
もはやインフラの役目を果たしています.
Ethereumウェブアプリを今後触っていく上で使うことになるでしょう.
ウォレットを作成したらアドレスを2つ作って下さい.
トークンの送金のテストをします.
以下からインストール可能です.
設定中に手に入る以下2つを使用するのでメモしておいて下さい.
- ニーモニック(12語からなる意味不明な文)
- アドレス2つ
今回はRopsten以外は使わないのでネットワークをRopstenに切り替えておいて下さい.
手順が分からない場合は以下の記事が分かりやすいです.
MyEtherWalletからMETAMASK(メタマスク)を使ってイーサリアム(ETH)送金
# Infura.ioへの接続設定
Truffleにも開発用Ethereumノードが同梱されています.
しかし、既にRopstenネットワークへの接続方法を知っています.
Infura.ioを使ってRopstenへ接続するように設定しましょう.
まずは接続用のライブラリをインストールします.
> npm install truffle-hdwallet-provider
ディレクトリに自動生成されているtruffle.jsを以下のように修正します.
mnemonic
とinfura_key
はあなたのものに置き換えてください.
Truffleの導入はこれで完了です.
ではトークンを実装していきましょう.
# Zeppelin Solidityの導入
# Zeppelin Solidityとは
Solidityでコントラクトを書くためのライブラリです.
多くのICOでも使用されています.
そのまま使うためにも、コードを読んで勉強するためにも必ずお世話になるはずです.
ICO
Initial Coin Offeringの略.仮想通貨による資金調達の手法。Ethereumのスマートコントラクトを使うと、資金の受取とトークンの送付を自動化できる。
# Zeppelin Solidityのインストール
以下のコマンドでインストールできます.
vagrant@vagrant:~/hello_token/truffle$ npm install zeppelin-solidity
# トークンの実装
contractsディレクトリにShunToken.solというファイル名で以下を実装して下さい.
コードの説明をします.
import "../../node_modules/zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
Zeppelin Solidityのコントラクトをインポートしています.
Zeppelin Solidityにはトークン発行に便利なコントラクトが多数実装されています.
そのうちStanrdardTokenとはERC20の規格を満たしたトークンです.
contract ShunToken is StandardToken {
コントラクトの継承です.
オブジェクト指向言語のクラスの継承と同様に、関数や状態変数(メンバ変数)を引き継ぎます.
多重継承が可能です.
address public owner;
address
型のowner
という状態変数を宣言しています.
public
という修飾子を状態変数につけると、ゲッターが自動で定義されます.
セッターは手動で定義する必要があります.
string public constant name = "ShunToken";
string public constant symbol = "SHN";
uint8 public constant decimals = 18;
トークンの概要を設定しています.
これらはEthereum上では意味はありません.
が、MetaMask始め多くのソフトウェアでその値が使われるので人間的には重要です.
name
とsymbol
は、Etherscanなどでトークンを見るとそのまま表示されます.
decimals
が一番分かりにくいかもしれません.
これはShunTokenの金額で、小数点をどこに表示したいかです.
例えば5000SHNの場合、
decimals = 0
で5000
、decimals = 3
で5.000
と表示されます.
Etherはdecimals = 18
で、多くのトークンが18
を採用しています.
uint256 public constant INITIAL_SUPPLY = 10000 * (10 ** uint256(decimals));
初期発行量です.
あえて計算しているのは、decimals
の値に関わらずに整数部を10000
とするためです.
function ShunToken() public {
コントラクトを同じ名前を持つ関数はコンストラクタです.
インスタンス化される際に呼ばれます.
オブジェクト指向言語と少し違うのは、
実行される(インスタンス化される)のはデプロイ時の1回限り
だということです.
owner = msg.sender;
このコントラクトインスタンスのオーナーをセットします.
後ほど初期トークンを付与するためです.
msg.sender
はコントラクトを呼び出したユーザー、または別のコントラクトです.
ここではコンストラクタ内なので、デプロイした私以外には有りえません.
totalSupply_ = INITIAL_SUPPLY;
uint256 thisSupply = totalSupply_ / 2;
uint256 ownerSupply = totalSupply_ - thisSupply;
balances[this] = thisSupply;
Transfer(0x0, this, thisSupply);
balances[owner] = ownerSupply;
Transfer(0x0, owner, ownerSupply);
totalSupply_
はZeppelin SolidityのStandardTokenで用意されている総発行するのパラメータです.
this
はコントラクト自身のアドレスを、owner
はデプロイした私のアドレスを表します.
それぞれ半分づつ付与しています.
Transfer
のように大文字で始まる関数に似たものは「イベント」といいます.
トークンの移動があった時にはTransfer(from, to, value)
イベントを発火することになっています.
function () payable {
payable
修飾子を関数につけると、Etherを受け取ることができるようになります.
特に、無名のpayable関数を「フォールバック関数」といいます.
コントラクトに直接(関数呼び出しではなく)Etherを送りつけると実行されます.
require(msg.value > 0);
require(bool)
は条件を満たさなかった場合に処理をそこで止めることができます.
msg.value
には送金したEtherの額が入ります.
0より多く送っていることを保証しています.
uint256 amount = msg.value * 10;
require(balances[this] >= amount);
balances[this] = balances[this].sub(amount);
balances[msg.sender] = balances[msg.sender].add(amount);
Transfer(this, msg.sender, amount);
受け取ったEtherの10倍の額が残高にあることを保証しています.
その後、コントラクトの口座から送金者の口座へ残高を移動しています.
# ローカルでトークンをテストする
それではTruffleを使って、まずはローカルでテストをしてみましょう.
migrations/2_deploy_contract.jsという名前で以下の内容を保存して下さい.
var ShunToken = artifacts.require("ShunToken");
module.exports = function(deployer) {
deployer.deploy(ShunToken);
};
なんとこれだけでデプロイ出来てしまいます.
ローカルにデプロイしてみましょう.
truffle develop
コマンドでローカルテスト用のノードとコンソールを立ち上げることができます.
vagrant@vagrant:~/hello_token/truffle$ ../node_modules/.bin/truffle develop
migrate
コマンドで先ほどの2_deploy_contract.jsを実行しデプロイ出来ます.
truffle(develop)> migrate --reset
デプロイ出来ているか確認してみましょう.
コントラクトがShunToken
という変数ですでに読み込まれています.
便利ですね.
以下のコマンドでインスタンスを取得し、shun
に代入します.
truffle(develop)> ShunToken.deployed().then(function(instance){shun=instance;})
残高を確認してみましょう.
truffle(develop)> shun.balanceOf(web3.eth.accounts[0])
{ [String: '5e+21'] s: 1, e: 21, c: [ 50000000 ] }
owner(デプロイしたアカウント)に確かに振り込まれていますね.
# トークンをRopstenにデプロイする
Ctrl+Dなどでコンソールを抜けて下さい.
公開ネットワークであるRopstenに接続します.
develop
ではなく以下のconsole
コマンドを使います.
vagrant@vagrant:~/hello_token/truffle$ ../node_modules/.bin/truffle console --network ropsten
デプロイはローカルで実行したコマンドを全く一緒です.
truffle(ropsten)> migrate --reset
実行が成功したらShunTokenのアドレスを確認しましょう.
truffle(ropsten)> ShunToken.address
'0xb4daaebd10f75f6b87a122734114a057777df697'
# MetaMaskでトークンを確認する
MetaMaskでRopstenネットワークを選択して下さい.
Account1を使用して下さい.
トークンタブのAdd Tokenで、先ほどのアドレスを入力して下さい.
SymbolとDecimalsが自動で入力されたかと思います.
追加して、5000SHN振り込まれていたら成功です.
# Etherを振り込んでSHNをもらう
ShunTokenではフォールバック関数を定義しましたね.
ShunTokenのアドレスに向けてEtherを送金してみましょう.
MetaMaskでAccount2に切り替えてください.
Sendからアドレスを入力して好きなだけEtherを送ってみて下さい.
1分ほど待ちマイニングされるとトークンが付与されます.
送金したEtherの10倍のトークンが振り込まれていれば成功です.
Truffle便利ですね.
# その先へ
以上、環境構築からトークン発行までお疲れ様でした.
このドキュメントの目標はチュートリアル終了後、
ドキュメントとソースコードを読んで開発を始められるようになることでした.
次に進むべき文献を少しだけ案内して終わりとします.
最後まで読んでいただきありがとうございました.
# 参考文献
- Ethereumホワイトペーパー日本語
- CryptoZombies – 楽しいゲーム形式のチュートリアル
- Zeppelin Solidity – セキュアなコードの見本
- Truffle公式ドキュメント
- Geth公式Wiki
- smart-contract-best-practices – コントラクトのセキュリティベストプラクティス
- EIPs – Ethereumの仕様に関する議論
- CryptoKitties コード