【Blockchain】SmartContractを手動コンパイルして、ローカル環境(Ganache)にデプロイ&テスト

`SmartContract` を開発するためのフレームワーク `Truffle.js` などがありますが、今回は、仕組みを理解するために手動でコンパイルして `Bytecode` と `ABI` (Application Binary Interface) を取得します。
そして、ローカル環境の `Blockchain` ネットワークにデプロイ&テストをします。

## Bytecode と ABI に関して

`Bytecode` (バイトコード)は、ブロックチェーンに上げるための `SmartContract` のバイナリコードです。`EVM` (Ethereum Virtual Machine) 仮想マシンによって実行されます。
`ABI` (Application Binary Interface) は、 `Bytecode` の機能や入力と出力などの情報が `Json` 形式で記載されています。`ABI` を見れば `SmartContract` にどういう機能を持っているのかがざっくり分かります。

## コンパイルするファイルたち

```
MyProkject/
├─ .vscode
│  └─ settings.json
├─ contracts
│  └─ SampleContract.sol
└─ scripts
   └─ compile.js
```

- `/.vscode/settings.json` に関しては、[こちらの記事](https://qiita.com/toshiok/items/7bb7cdae0200543c74d3)をご参照ください。
- `/contracts/` フォルダーには、開発する `SmartContract` プログラムを入れます。この例では、 `SampleContract.sol` を入れました。
- `/scripts/compile.js` には、コンパイルするための `Javascript` で記載したスクリプトを入れます。

```js:compile.js
// ライブラリのインポート
const path = require('path');   // ファイルパスを操作するためのライブラリ
const fs = require('fs');       // ファイルを読み書きするためのライブラリ
const solc = require('solc');   // Solidity言語のプログラムをコンパイルするためのライブラリ
const chalk = require('chalk'); // ログ出力に色をつけるためのライブラリ

// プログラムファイルのパスを設定
const contractPath = path.resolve(__dirname, "../contracts", "SampleContract.sol");

// プログラムファイルを読み込む
const source = fs.readFileSync(contractPath, 'utf8');

// プログラムをコンパイルし、Bytecode と ABI を取得する
const {abi, bytecode} = solc.compile(source, 1).contracts[':SampleContract'];

// Bytecode と ABI をログ出力する
console.log(chalk.green(bytecode));
console.log(chalk.cyan(abi));
```

## コンパイル

ターミナルでプロジェクトのパスを開いて、次のコマンドを実行します。

```sh
node ./scripts/compile.js 
```

すると、コンパイルが行われ、結果としてログに緑色で `Bytecode` とシアン色で `ABI` が出力されます。
でも、これだと `Bytecode` と `ABI` をログ出力するだけなので、次は、ローカル環境に `Blockchain` の開発用のネットワーク環境を構築して、そこに開発した `SmartContract` をデプロイしたいと思います。

## Ganache
`Ganache` は、 `Blockchain` の開発用のネットワーク環境です。

- 次のアドレスからダウンロード、インストールします。  
https://truffleframework.com/ganache

- 立ち上げると、次の画面が表示されるので、「QUICKSTART」をクリックします。

![20190507_Ganache_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273166/79f55158-8604-0871-06e0-1180d92a3d43.png)

- すると、ネットワークが構築され、100ETHを持つ10のアカウントが用意されます。

![20190507_Ganache_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273166/b117d825-c438-1118-fc33-d5945edf7528.png)

- `BLOCKS` タブをクリックすると、ネットワークに保持される `Blockchain` が表示されます。最初は、開始ブロックである `BLOCK 0` だけが存在します。このブロックは `Block Genesys` とも呼ばれています。

![20190507_Ganache_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273166/373ec0a7-4b4f-65a0-8138-82565fc2a8a4.png)

## Deploy & Test

- テスト用のライブラリをインストールします。

```sh
npm install --save-dev mocha@{バージョン}
```

- `/scripts/compile.js` を次のように変更します。

```js:compile.js
// ライブラリのインポート
const path = require('path');   // ファイルパスを操作するためのライブラリ
const fs = require('fs');       // ファイルを読み書きするためのライブラリ
const solc = require('solc');   // Solidity言語のプログラムをコンパイルするためのライブラリ

// プログラムファイルのパスを設定
const contractPath = path.resolve(__dirname, "../contracts", "SampleContract.sol");

// プログラムファイルを読み込む
const source = fs.readFileSync(contractPath, 'utf8');

// プログラムをコンパイルし、外部へ export する
module.exports = solc.compile(source, 1).contracts[':SampleContract'];
```

- テスト用のスクリプト `/test/SampleContract.test.js` を作成します。

```js:SampleContract.test.js
// ライブラリのインポート
const assert = require('assert');  // テスト assert をするためのライブラリ
const Web3 = require('web3');      // ローカル環境(Ganache)と接続するためのライブラリ

// ローカル環境(Ganache)と接続するためのオブジェクトを生成
const provider = new Web3.providers.HttpProvider("HTTP://127.0.0.1:7545");
const web3 = new Web3(provider);

// コンパイルスクリプトからBytecodeとABIを取得する
const {abi, bytecode} = require('../scripts/compile');

// 変数定義
let accounts;        // Ganacheで提供される10アカウントを保持する
let sampleContract;  // デプロイしたSmartContractを保持する

// 各テストを実行する前に実行する
beforeEach(async() => {
    // アカウントを取得する
    accounts = await web3.eth.getAccounts();
    // ABIとBytecodeを一番最初のアカウントを使ってデプロイ
    usersContract = await new web3.eth.Contract(JSON.parse(abi))  // ABI
        .deploy({data: bytecode})                                 // Bytecode
        .send({from: accounts[0], gas: '1000000'});               // 一番最初のアカウント、GAS=1000000
});

// テスト 'The UsersContract' 開始
describe('The UsersContract', async() => {
    // １つ目のテスト: デプロイできたか？
    it('should deploy', () => {
        console.log(usersContract.options.address);
        assert.ok(usersContract.options.address);
    });
});
```

- `/package.json` の `"test": "mocha"` にする。

```json
{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "mocha"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "chalk": "^2.4.2",
    "mocha": "^5.2.0",
    "solc": "^0.4.24",
    "web3": "^1.0.0-beta.35"
  }
}
```

- 次のコマンドでテストを実行して、Deployを確認します。

```sh
npm run test
```

- 成功すると、ログに次のような出力を確認できます。

```sh
> myproject@1.0.0 test /myproject
> mocha

  The UsersContract
0xdabBD1955e2873bB498A6F1f149A8A0d74f48170
    ✓ should deploy


  1 passing (72ms)
```

- また、 `Ganache` の `TRANSACTIONS` タブで `SmartContract` のデプロイができたことも確認できます。

![20190507_Ganache_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273166/7ad543c4-f687-95ac-9b1a-663f84be8ca3.png)