TruffleとGanacheを使ったÐapps開発の環境構築 ＋ チュートリアル



#はじめに
本記事は [DSL Advent Calendar 2019](https://qiita.com/advent-calendar/2019/muroran-it-dsl)　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開発フレームワークです。
スマートコントラクトのコンパイル、デプロイ、テストなど便利な機能がたくさんあり、チュートリアルも充実しています。

```shell-session

npm install -g truffle
```

###Ganacheインストール
Ganacheは簡単に作成することができるEthereumのプライベートネットワークです。
起動してすぐにEthを持つアカウントが作成されたり、チェーンのリセットを行えたりと、
Ðappsを開発したりテストしたりするのに非常に便利です。デスクトップアプリとしてもコマンドラインツールとしても使えます。

[公式サイト](https://www.trufflesuite.com/ganache)でインストーラーをダウンロードしてインストールします。

#Hello Worldまでのチュートリアル
Truffleを使って「Hello World」コントラクトを作成し、Ganacheで作ったEthereumのプライベートネットワークにデプロイして、動作確認をしてみます。

##Truffleプロジェクト作成
まずはプロジェクトディレクトリを作り、初期化します。
適当な場所で`truffle init`をすることでプロジェクトを作成することができます。

今回は以下のようにhello_worldディレクトリを作成しました。
うまくいくとコンソールに成功ログが出て、いくつかのファイルが作成されます。

```shell-session

$ 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


```
以下のようなツリーが生成されます。

``` shell-session
$ 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通信を行うため、それにあわせます。

```javascript
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を選択します。<img width="1440" alt="ganache_new_workspace.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403459/c770b516-411b-66c6-7777-8c5670488591.png">

ワークスペースの名前設定と「リンク」を行うための設定画面に移ります。

ADD PRPJECTをクリックして、先ほど作成したプロジェクトディレクトリの`truffle-config.js`を追加することで、
既存のTruffleプロジェクトとGanacheのワークスペースをリンクさせることができます。

下の画像のように、先ほど作ったTruffleプロフェクトをリンクさせました。リンクは後からでも行えますし、アンリンクすることもできます。

<img width="1440" alt="ganache_add_workspace.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403459/5d9220c2-fa09-d0d5-41ef-e556619c47af.png">

成功すると100ETHをもったアカウントが自動的に１０個生成されます。便利ですね。
開発中は基本的に付けっぱなしでOKです。

<img width="1440" alt="ganache_first.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403459/cdc6b7e9-bd32-481e-0b09-3eeac359653d.png">

これでEthereumのプライベートネットワークをローカルに作ることができました。

####確認
TruffleからGanacheのプライベートネットワークに接続できるか確認してみます。
プロジェクトディレクトリで`truffle console`と打つとコンソールへ接続できます。

```shell-session
$ truffle console
truffle(development)> 

```
こうなればOKです。コンソールから送金などの操作もできます。
`.exit`をタイプするか`Ctrl + C`を2回押せばコンソールから抜けられます。

ちなみに、ネットワークが見つからないと以下のようなエラーがでます。
`truffle-config.js`の設定を見直したりしてみましょう。

```shell-session
$ 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コマンドで作成します。

```shell-session
$ truffle create contract HelloWorld
```

これで`contracts`の中に`HelloWorld.sol`ができます。
プログラムを書いていきます。

/contracts/HelloWorld.sol

```javascript
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`の２種類があり、`storage`はブロックチェーンにデータを保存しますが、`memory`は保存しません。
なので、`getMessage()` は `string`をリターンしますが、ブロックチェーンに記録することはありません。

##コンパイル

```shell-session
$ 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コマンドで作成します。

```shell-session
$ truffle create migration HelloWorld
```

`migrations`の中に`15xxxxxxxx_hello_world.js`が作成されました。
マイグレーションファイルを作成すると、ファイル名の先頭にファイル作成時のタイムスタンプが設定されます。
ファイルの中身を記述します。

migrations/15xxxxxxxx_hello_world.js

```javascript
const HelloWorld = artifacts.require("HelloWorld");
module.exports = function(_deployer) {
  // Use deployer to state migration tasks.
  _deployer.deploy(HelloWorld)
};
```

##デプロイ
migrateコマンドで`migrate`ディレクトリ配下のスクリプトを実行していきます。

```shell-session
$ truffle migrate
```
いろいろとログが出て成功しました

```shell-session
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コマンドは前回のデプロイが成功だった場合、それ以降の新しいマイグレーションだけ実行します。
なので、一度デプロイしたコントラクトコードを修正した場合は以下のコマンドを実行することで
新しいコントラクトコードでデプロイしなおす必要があります。

```shell-session
$ truffle migrate --reset
```

####確認
２回マイグレートを行ったのでGanacheでネットワークのログを確認してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403459/ce542195-a22e-e509-5592-87adfea0b0a9.png)

先頭のアカウントのETH残高が減っています。トランザクションのログをみてみると８個のログが記録されていました。
ここで、簡単にログの読み方を解説します。

 - 0xから始まるハッシュ値（ TX HASH )はトランザクションハッシュです。EthereumではETHの送金もスマートコントラクトの実行もトランザクションをネットワークに送信することで行います。ここでは個々のトランザクションの識別子であると考えてもらえれば大丈夫です。
 - FROM ADDRESSはトランザクションの送信者です。
 - GAS USEDはトランザクションの処理にかかった「コスト」です。
 - VALUEはトランザクションで「ETHの送金」が行われていた場合のETHの量です。今回のトランザクションでは送金を行っていないので０です。

ログ
<img width="1440" alt="ganache_migrateLig.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403459/92ffb615-e188-7553-73dd-a0a18251653b.png">
ログの下から順にコントラクトの作成、コントラクトの実行、コントラクトの作成、コントラクトの実行・・・と行われています。
ここでは下から４つ目までのログにフォーカスを当てて見ていきます。この４つが`truffle migrate`を１回実行した際のログになっています。

デプロイは`migrations`のファイル名先頭のタイムスタンプの順番に行われるため、最初のコントラクト生成ログ（CONTRACT CREATION）はMigrationsコントラクトがネットワーク上にデプロイされたことを意味します。また、truffleコマンドを実行した際のコンソールのログに書かれていたTxハッシュとGanacheのTxハッシュが一致していることがわかります。
コントラクトをチェーンにデプロイすると、コントラクトにContract Addressが与えられ、このアドレスを使ってコントラクトを呼び出すことができます。CREATED CONTRACT ADDRESSが、このトランザクションで与えられたアドレスを示しています。

Migrationsコントラクトは`truffle init`で自動生成されるファイルで、「現在どのマイグレーションまで完了しているか」を記憶するコントラクトです。
contracts/Migrations.sol

```javascript
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()`を呼び出してみます。

```shell-session
truffle(development)> let helloWorld = await HelloWorld.deployed()
truffle(development)> helloWorld.getMessage()
'Hello World'
```
うまくいきました。

#まとめ
簡単にですが、以上がTruffleとGanacheを使った環境構築とスマートコントラクト開発の流れになります。
まだ紹介できていないTruffleのテストの機能などもいつかまとめれたらいいなと思っています。

#参考
[Truffle公式のチュートリアル](https://www.trufflesuite.com/docs/truffle/quickstart)
[Ganache公式のチュートリアル](https://www.trufflesuite.com/docs/ganache/quickstart)
[Truffle + Ganache + Metamask を使って Ðapps の世界に旅立とう](https://tech.drecom.co.jp/go_to_the_dapps_world/)
[Truffle の Migration は何をやっている？](https://qiita.com/blueplanet/items/e3f5590c9711df4d4845)
[Ethereum入門](https://book.ethereum-jp.net/)