Ethereum開発チュートリアル – GethからTruffleでERC20トークン発行まで

Calendar Clock iconCalendar Clock icon

イーサリアム

# 目次

こんにちは、シュンです.

この記事は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

GO言語で実装された公式Ethereumクライアントです.

コマンドはgeth(ゲス)です.

他の言語でも、C++やPythonなどで実装されたものもあります.

GO言語で実装されているといってもGO言語が出てくることはありません.

知っている必要はありません.

gethでは以下のことが出来ます.

  • EthereumのP2Pノードの立ち上げ
    • プライベートネットワーク立ち上げ
    • 開発用、本番用(Main)ネットワークへの参加
  • JavascriptのREPLを使ったインタラクション
    • Ethereumアカウント作成
    • マイニング
    • コントラクト公開

などなど.

REPL

Read-eval-print loopの略

字のごとく読んで評価して表示して繰り返す.対話型評価環境を指す。ただし、インタープリタと同義ではない。

出典: http://d.hatena.ne.jp/keyword/REPL

# 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上のアプリは、

  1. ネットワークに参加しているノード
  2. JSON RPCでノードを操作するフロントエンド

の2つが必要になります.

RPC

リモートプロシージャコールとは、ネットワークによって接続された他のコンピュータ上でプログラムを呼び出し、実行させるための手法のことである.あるいは、そのためのプロトコルのことである。

出典:https://www.weblio.jp/content/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

Infura.ioが管理しているEthereumのノードを利用できるサービスです.

本番用と開発用ノードが揃っています.

無料で利用できます.

# Infura.ioに登録する

こちらから登録して下さい.

https://infura.io/signup

メールが届きます.

そこにメインネット(本番)、開発用ネットのノードへ接続するためのURLが記載されています.

https://mainnet.infura.io/XXXXXXXXXXXXXXXXXXXXX (本番)

https://ropsten.infura.io/XXXXXXXXXXXXXXXXXXXXX (開発用)

# 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では

  1. Zaifなどの取引所で法定通貨と交換する
  2. マイニングする
  3. 誰かに送ってもらう

のいずれの方法を取る必要があります.

Ropstenなどの開発用ネットでは、Etherを送ってくれるサービスがあります.

今回はこれを利用します.

以下のページにアクセスして下さい.

Throttled Testnet Faucet

先ほどコピーしたアドレスを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つです.

  1. web3.eth.getBalanceaddressアカウントに記録されている金額をInfura.ioに問い合わせる
  2. 単位がWeiなので、web3.utils.fromWeiでEtherに変換する
  3. コンソールに出力する

なぜ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.0005Etherでした.

# 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: 実際に消費されたgas
  • Gas Price 設定したgasの価格
  • Actual Tx Cost/Fee: 上記をかけ合わせた手数料(Ether)

計算してみましょう.

オレの残高は送金前2Etherでした.

送金額は0.1Ether、手数料は0.000168Etherです.

> 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という名前で保存して下さい.

以下のコマンドを実行するとabibinファイルが書き出されます.

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に切り替えておいて下さい.

MetaMask for Google Chrome

MetaMask for Firefox

手順が分からない場合は以下の記事が分かりやすいです.

MyEtherWalletからMETAMASK(メタマスク)を使ってイーサリアム(ETH)送金

# Infura.ioへの接続設定

Truffleにも開発用Ethereumノードが同梱されています.

しかし、既にRopstenネットワークへの接続方法を知っています.

Infura.ioを使ってRopstenへ接続するように設定しましょう.

まずは接続用のライブラリをインストールします.

> npm install truffle-hdwallet-provider

ディレクトリに自動生成されているtruffle.jsを以下のように修正します.

mnemonicinfura_keyはあなたのものに置き換えてください.

Truffleの導入はこれで完了です.

ではトークンを実装していきましょう.

# Zeppelin Solidityの導入

# 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始め多くのソフトウェアでその値が使われるので人間的には重要です.

namesymbolは、Etherscanなどでトークンを見るとそのまま表示されます.

decimalsが一番分かりにくいかもしれません.

これはShunTokenの金額で、小数点をどこに表示したいかです.

例えば5000SHNの場合、

decimals = 05000decimals = 35.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便利ですね.

# その先へ

以上、環境構築からトークン発行までお疲れ様でした.

このドキュメントの目標はチュートリアル終了後、

ドキュメントとソースコードを読んで開発を始められるようになることでした.

次に進むべき文献を少しだけ案内して終わりとします.

最後まで読んでいただきありがとうございました.

# 参考文献

  1. Ethereumホワイトペーパー日本語
  2. CryptoZombies – 楽しいゲーム形式のチュートリアル
  3. Zeppelin Solidity – セキュアなコードの見本
  4. Truffle公式ドキュメント
  5. Geth公式Wiki
  6. smart-contract-best-practices – コントラクトのセキュリティベストプラクティス
  7. EIPs – Ethereumの仕様に関する議論
  8. CryptoKitties コード

リモートフリーランス。ウェブサービス、スマホアプリエンジニア。
東アジアを拠点に世界を移動しながら活動してます!

お仕事のご依頼・お問い合わせはこちら

コメント