#はじめに
本記事は DSL Advent Calendar 2019 2日目の記事です。M1の @romorimori が担当します。
###Ðappsとは
ブロックチェーンプラットフォームの一つであるEthereumでは、
分散ネットワーク上で実行可能なスマートコントラクトと呼ばれるプログラムを開発することができます。
スマートコントラクトの仕組み自体はオブジェクト指向での「クラス」に似ていて、
クラス変数に相当するような内部状態を保持するためのストレージ部分と
関数やメソッドに相当するような実行コードであるコントラクト・コードを持っています。
スマートコントラクトは分散ネットワーク上にデプロイされ、ユーザが実行命令を出すことで実行することができます。
コントラクト・コードに任意の動作をプログラムすることで、特定管理者が存在せずともユーザー間で直接データをやり取り可能になります。
これを利用したブロックチェーン上で動くアプリケーションのことをDapps(Decentralized Applications)と呼びますが。
Ðはイーサと読むので、Ethereumを使ったDappsのことを特に、Ðappsと呼びます。
この記事ではÐappsを開発するための環境構築と、シンプルなÐappsを実際に作るところまでをやっていきます。
###Solidityについて
コントラクト・コードはEthereumネットワーク上で実行されていると書きましたが、
厳密にはEthereumに接続されているノード内のEVMと呼ばれる専用の仮想マシン上で実行されています。
ここでは「EVM Code」と呼ばれるバイトコードの形式で記述され処理されているのですが、
EVM Codeは低水準な機械語であるため、人間には可読性が悪いものとなってます。
そこで、EVM Codeにコンパイル可能な言語が複数開発されています。
現時点ではSolidityと呼ばれるJavascriptに近い言語がよく使われています。
この記事でもSolidityを使ってプログラムを記述していきます。
#環境
MacOS Catalina 10.15.1
node v11.14.0
npm 6.7.0
#作業の手順
- Truffle & Ganacheをインストール
- Truffleプロジェクト作成
- GanacheとTruffleプロジェクトをリンク
- コントラクトコード作成、デプロイ
- 動作確認
#環境構築
###Truffleインストール
TruffleはEVM Code開発フレームワークです。
スマートコントラクトのコンパイル、デプロイ、テストなど便利な機能がたくさんあり、チュートリアルも充実しています。
npm install -g truffle
###Ganacheインストール
Ganacheは簡単に作成することができるEthereumのプライベートネットワークです。
起動してすぐにEthを持つアカウントが作成されたり、チェーンのリセットを行えたりと、
Ðappsを開発したりテストしたりするのに非常に便利です。デスクトップアプリとしてもコマンドラインツールとしても使えます。
公式サイトでインストーラーをダウンロードしてインストールします。
#Hello Worldまでのチュートリアル
Truffleを使って「Hello World」コントラクトを作成し、Ganacheで作ったEthereumのプライベートネットワークにデプロイして、動作確認をしてみます。
##Truffleプロジェクト作成
まずはプロジェクトディレクトリを作り、初期化します。
適当な場所でtruffle init
をすることでプロジェクトを作成することができます。
今回は以下のようにhello_worldディレクトリを作成しました。
うまくいくとコンソールに成功ログが出て、いくつかのファイルが作成されます。
$ mkdir hello_world
$ cd hello_world/
$ truffle init
✔ Preparing to download
✔ Downloading
✔ Cleaning up temporary files
✔ Setting up box
Unbox successful. Sweet!
Commands:
Compile: truffle compile
Migrate: truffle migrate
Test contracts: truffle test
以下のようなツリーが生成されます。
$ tree
hello_world
├── contracts
│ └── Migrations.sol
├── migrations
│ └── 1_initial_migration.js
├── test
└── truffle-config.js
3 directories, 3 files
####生成されたファイルの説明
-
contracts
: この中にSolidityで記述されたプログラムを配置します。 -
migrations
: このディレクトリ内のファイルはマイグレーションファイルと呼ばれ、作成したコントラクトをネットワークにデプロイするために使います。 -
test
: コントラクトが正しく動作するかを確認するためのテストコードが配置されます。 -
truffle-config.js
: Truffleの設定ファイルです。詳細は以下で説明します。
##Truffle設定
Ganacheで作るプライベートネットワークと連携するために、configファイルを変更し、ネットワークの設定をします。
Ganacheはデフォルトだとlocalhost:7545でRPC通信を行うため、それにあわせます。
module.exports = {
networks: {
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 7545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
}
}
}
##Ganache起動
インストールしたGanacheを起動すると以下の画面が表示されるのでNEW WORKSPACEを選択します。
ワークスペースの名前設定と「リンク」を行うための設定画面に移ります。
ADD PRPJECTをクリックして、先ほど作成したプロジェクトディレクトリのtruffle-config.js
を追加することで、
既存のTruffleプロジェクトとGanacheのワークスペースをリンクさせることができます。
下の画像のように、先ほど作ったTruffleプロフェクトをリンクさせました。リンクは後からでも行えますし、アンリンクすることもできます。
成功すると100ETHをもったアカウントが自動的に10個生成されます。便利ですね。
開発中は基本的に付けっぱなしでOKです。
これでEthereumのプライベートネットワークをローカルに作ることができました。
####確認
TruffleからGanacheのプライベートネットワークに接続できるか確認してみます。
プロジェクトディレクトリでtruffle console
と打つとコンソールへ接続できます。
$ truffle console
truffle(development)>
こうなればOKです。コンソールから送金などの操作もできます。
.exit
をタイプするかCtrl + C
を2回押せばコンソールから抜けられます。
ちなみに、ネットワークが見つからないと以下のようなエラーがでます。
truffle-config.js
の設定を見直したりしてみましょう。
$ truffle console
> Something went wrong while attempting to connect to the network. Check your network configuration.
Could not connect to your Ethereum client with the following parameters:
- host > 127.0.0.1
- port > 7545
- network_id > *
Please check that your Ethereum client:
- is running
- is accepting RPC connections (i.e., "--rpc" option is used in geth)
- is accessible over the network
- is properly configured in your Truffle configuration file (truffle-config.js)
##HelloWorldコントラクトを作成
コントラクトファイルもtruffleコマンドで作成します。
$ truffle create contract HelloWorld
これでcontracts
の中にHelloWorld.sol
ができます。
プログラムを書いていきます。
/contracts/HelloWorld.sol
pragma solidity ^0.5.0;
contract HelloWorld {
string defaultMessage;
constructor() public {
defaultMessage = 'Hello World';
}
function getMessage() public view returns(string memory){
return defaultMessage;
}
}
いわゆるクラス宣言のようにcontract
宣言をして、その中に変数やメソッドを記述します。
今回はdefaultMessage
を宣言し、constructor()
内でメッセージを設定します。コントラクトが作られた時constructor()
が一度だけ実行されます。
Solidityではデータの保存にstorage
とmemory
の2種類があり、storage
はブロックチェーンにデータを保存しますが、memory
は保存しません。
なので、getMessage()
は string
をリターンしますが、ブロックチェーンに記録することはありません。
##コンパイル
$ truffle compile
Compiling your contracts...
===========================
> Compiling ./contracts/HelloWorld.sol
> Compiling ./contracts/Migrations.sol
> Artifacts written to ~/hello_world/build/contracts
> Compiled successfully using:
- solc: 0.5.12+commit.7709ece9.Emscripten.clang
コンパイルできました。成果物はbuild/contracts
フォルダ直下に配置されます。
コードに不備があった場合はここでエラーが出て弾かれます。
##マイグレーションファイル作成
HelloWorld コントラクトをネットワークにデプロイするためのマイグレーションファイルを作成します。
これもtruffleコマンドで作成します。
$ truffle create migration HelloWorld
migrations
の中に15xxxxxxxx_hello_world.js
が作成されました。
マイグレーションファイルを作成すると、ファイル名の先頭にファイル作成時のタイムスタンプが設定されます。
ファイルの中身を記述します。
migrations/15xxxxxxxx_hello_world.js
const HelloWorld = artifacts.require("HelloWorld");
module.exports = function(_deployer) {
// Use deployer to state migration tasks.
_deployer.deploy(HelloWorld)
};
##デプロイ
migrateコマンドでmigrate
ディレクトリ配下のスクリプトを実行していきます。
$ truffle migrate
いろいろとログが出て成功しました
Starting migrations...
======================
> Network name: 'development'
> Network id: 5777
> Block gas limit: 0x6691b7
1_initial_migration.js
======================
Deploying 'Migrations'
----------------------
> transaction hash: 0xe6cc1b0d4f3e8a79c64be1e9a0f0daf9bae7908bfc76ee075b10c5f0cbc0364d
> Blocks: 0 Seconds: 0
> contract address: 0x679357F71B9B097B6546188DAb4E47aDA516DB63
> block number: 1
> block timestamp: 1575113348
> account: 0x2cC796eCE69f4Bf41f4Cc6D3733FE362D921F9a5
> balance: 99.99472518
> gas used: 263741
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00527482 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00527482 ETH
1575112675_hello_world.js
=========================
Deploying 'HelloWorld'
----------------------
> transaction hash: 0x6fcb1f9fd8bd7761c676448d64c571da7d7945fe25ec599dba6e2ce8e17edbe9
> Blocks: 0 Seconds: 0
> contract address: 0x8300Be5250A6323A7A83d220157e4a6c2A980659
> block number: 3
> block timestamp: 1575113348
> account: 0x2cC796eCE69f4Bf41f4Cc6D3733FE362D921F9a5
> balance: 99.99000202
> gas used: 194135
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.0038827 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.0038827 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.00915752 ETH
ここで補足なのですが、migrateコマンドは前回のデプロイが成功だった場合、それ以降の新しいマイグレーションだけ実行します。
なので、一度デプロイしたコントラクトコードを修正した場合は以下のコマンドを実行することで
新しいコントラクトコードでデプロイしなおす必要があります。
$ truffle migrate --reset
####確認
2回マイグレートを行ったのでGanacheでネットワークのログを確認してみます。
先頭のアカウントのETH残高が減っています。トランザクションのログをみてみると8個のログが記録されていました。
ここで、簡単にログの読み方を解説します。
- 0xから始まるハッシュ値( TX HASH )はトランザクションハッシュです。EthereumではETHの送金もスマートコントラクトの実行もトランザクションをネットワークに送信することで行います。ここでは個々のトランザクションの識別子であると考えてもらえれば大丈夫です。
- FROM ADDRESSはトランザクションの送信者です。
- GAS USEDはトランザクションの処理にかかった「コスト」です。
- VALUEはトランザクションで「ETHの送金」が行われていた場合のETHの量です。今回のトランザクションでは送金を行っていないので0です。
ログ
ログの下から順にコントラクトの作成、コントラクトの実行、コントラクトの作成、コントラクトの実行・・・と行われています。
ここでは下から4つ目までのログにフォーカスを当てて見ていきます。この4つがtruffle migrate
を1回実行した際のログになっています。
デプロイはmigrations
のファイル名先頭のタイムスタンプの順番に行われるため、最初のコントラクト生成ログ(CONTRACT CREATION)はMigrationsコントラクトがネットワーク上にデプロイされたことを意味します。また、truffleコマンドを実行した際のコンソールのログに書かれていたTxハッシュとGanacheのTxハッシュが一致していることがわかります。
コントラクトをチェーンにデプロイすると、コントラクトにContract Addressが与えられ、このアドレスを使ってコントラクトを呼び出すことができます。CREATED CONTRACT ADDRESSが、このトランザクションで与えられたアドレスを示しています。
Migrationsコントラクトはtruffle init
で自動生成されるファイルで、「現在どのマイグレーションまで完了しているか」を記憶するコントラクトです。
contracts/Migrations.sol
pragma solidity >=0.4.21 <0.6.0;
contract Migrations {
address public owner;
uint public last_completed_migration;
constructor() public {
owner = msg.sender;
}
modifier restricted() {
if (msg.sender == owner) _;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
次のコントラクト呼び出しログでは、function setCompleted(uint completed)
に1
を引数にとり呼び出しています。これにより、「現在マイグレーションは1
まで完了している」という情報をチェーンに保存しています。
次のコントラクト生成ログではHelloWorldコントラクトが生成され、
その次のコントラクト呼び出しログではMigrationコントラクトを再び呼び出してHelloWorldコントラクトを記録します。
これにより、次回以降マイグレーションコマンドを叩いた時、HelloWorldコントラクト以降のMigrationファイルのみ実行されるようになります。
##HelloWorldの動作確認
デプロイがうまくいったかどうか、コンソールからHelloWorldコントラクトの動作確認をしてみます。
デプロイすると、Truffleが提供してくれているJavascriptオブジェクトを使うことで、簡単にコントラクトが利用できるようになります。
deployed()
でコントラクトにアクセスして、getMessage()
を呼び出してみます。
truffle(development)> let helloWorld = await HelloWorld.deployed()
truffle(development)> helloWorld.getMessage()
'Hello World'
うまくいきました。
#まとめ
簡単にですが、以上がTruffleとGanacheを使った環境構築とスマートコントラクト開発の流れになります。
まだ紹介できていないTruffleのテストの機能などもいつかまとめれたらいいなと思っています。
#参考
Truffle公式のチュートリアル
Ganache公式のチュートリアル
Truffle + Ganache + Metamask を使って Ðapps の世界に旅立とう
Truffle の Migration は何をやっている?
Ethereum入門