Truffleのexternal compilersを試す

こんにちは。皆さんTruffle使っていますか！？

弊社（ZEROBILLBANK）ではスマートコントラクトのローカル開発環境としてtruffleとganache-cliをよく使っています。

つい先日、独自のスクリプトでSolidityをコンパイルして、その結果をTruffleに喰わせてテストなどを実行したい場面がありました。
調べたところ「external compilers」で上記のような事が出来そうだったので試してみました。 
 https://www.trufflesuite.com/docs/truffle/reference/configuration#external-compilers

今回は簡単なサンプルでexternal compilersを使ってみます。

### 前提

以降の話はnpm initでプロジェクトを作成し、truffleを導入済みの前提で進めます。
また、ソースコードは以下に公開してあります。
https://github.com/shushutochako/truffle_external_compiler_sample

## コントラクトの作成

確認用にコンパイルするコントラクトを作成します。コンパイルを試すだけなので値のsetとgetが可能なだけの単純なものです。

__contracts/Test.sol__

```
pragma solidity >=0.4.21 <0.7.0;

contract Test {
  uint private _val;
  constructor(uint val) public {
    _val = val;
  }

  function setVal(uint newVal) public {
    _val = newVal;
  }

  function getVal() public view returns (uint) {
    return _val;
  }
}
```

## コンパイルスクリプトの追加

external compilerとして使用するスクリプトを追加します。
サンプルではsolc.jsを使って自分でコンパイルをしています。

__compiler/compile.js__

```compiler/compile.js
#! /bin/node

const solc = require('solc');
const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);

(async() => {
  console.log('\nSTART Compile!!');

  const SOL_NAME = 'Test';

  const sol = await readFile(`${__dirname}/../contracts/${SOL_NAME}.sol`, 'utf8');
  const input = {
    language: 'Solidity',
    sources: {
      'Test.sol': {
        content: sol
      }
    },
    settings: {
      outputSelection: {
        '*': {
          '*': ['*']
        }
      }
    }
  };
  
  const output = JSON.parse(solc.compile(JSON.stringify(input)));
  Object.keys(output.contracts).forEach( async (key) => {
    const abi = JSON.stringify(output.contracts[key][SOL_NAME].abi);
    const bytecode = `0x${JSON.stringify(output.contracts[key][SOL_NAME].evm.bytecode.object)}`.replace(/"/g, '');
    await writeFile(`${__dirname}/outputs/${key}.abi`, abi);
    await writeFile(`${__dirname}/outputs/${key}.bytecode`, bytecode);
  });
  
  console.log('END Compile!!\n')
})()
.catch(err => {
  console.error('compile failed.');
  console.error(err);
}) ;
```

#### abiとbytecodeの書き出し

下記の部分でコンパイル結果のabiとbytecodeをcompiler/outputsディレクトリにファイル出力しています。 後ほどcompilerの設定でここで出力したファイルを指定します。


```js
 Object.keys(output.contracts).forEach( async (key) => {
    const abi = JSON.stringify(output.contracts[key][SOL_NAME].abi);
    const bytecode = `0x${JSON.stringify(output.contracts[key][SOL_NAME].evm.bytecode.object)}`.replace(/"/g, '');
    await writeFile(`${__dirname}/outputs/${key}.abi`, abi);
    await writeFile(`${__dirname}/outputs/${key}.bytecode`, bytecode);
  });
```

## truffle-config.jsの変更

truffle initで初期セットアップした後はcompilersの設定は以下のようになっています。
この部分を上で追加したスクリプトで出力したabiとbytecodeを使用するように変更します。

```js
compilers: {
  solc: {
    // version: "0.5.1",    // Fetch exact version from solc-bin (default: truffle's version)
    // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
    // settings: {          // See the solidity docs for advice about optimization and evmVersion
    //  optimizer: {
    //    enabled: false,
    //    runs: 200
    //  },
    //  evmVersion: "byzantium"
    // }
  }
}
```

↓

```js
compilers: {
  external: {
    command: "node compile",
    workingDirectory: "./compiler",
    targets: [{
      properties: {
        contractName: "Test",
      },
      fileProperties: {
        abi: "./outputs/Test.sol.abi",
        bytecode: "./outputs/Test.sol.bytecode",
      }
    }]
  }
}
```

- workingDirectory
    - コンパイルコマンドの実行パスや生成物のパス指定する際のベースとなるディレクトリを指定します。今回は「outputs」ディレクトリと「compile.js」は共に「compiler」ディレクトリ配下にあるので、compilerを指定しています。
- command
    - コンパイルで実行するコマンドを指定します。 上で作成したcompile.jsを実行したいので「node compile」を指定しています。
- targets
    - 各コントラクトの設定を配列で指定します。今回のサンプルはTest.solのみなので1件のみです。「abi」「bytecode」欄にコンパイルスクリプトで出力したファイルをそれぞれ指定しています。 

## 動作確認

設定が全て出来たので、動作確認してみましょう。

```console
$ npx truffle compile                                                     

Compiling your contracts...
===========================
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/Test.sol

START Compile!!
END Compile!!

$
$ ls -1 compiler/outputs
Test.sol.abi
Test.sol.bytecode
$
$ ls -1 build/contracts                                                   
Migrations.json
Test.json
$
```

「START〜」、「END〜」のログから分かるように、作成したスクリプトが実行されていることが分かります。
「compiler/outputs」を確認するとabiとbytecodeのファイルが出力されています。また、buildディレクトリにはtruffleのビルド結果が出力されました。

コンパイルは問題が無さそうなので、テストを追加して実行も試してみます。

__test/test.spec.js__

```test/test.spec.js
const Test = artifacts.require("Test");

contract("Test", () => {
  it("normal case", async () => {
    const instance = await Test.new(5, { gas: 300000 });
    assert.equal(5, await instance.getVal());
    await instance.setVal(10, { gas: 300000 });
    assert.equal(10, await instance.getVal());
  });
});
```

```console
$ npx truffle test                                                        
Using network 'test'.


Compiling your contracts...
===========================
> Compiling ./contracts/Test.sol

START Compile!!
END Compile!!




  Contract: Test
    ✓ normal case (221ms)


  1 passing (261ms)

$
```

テストも想定通りにパスしました！！
このようにexternal compilerを使うことで色々なことを前処理で実行出来そうですね。

今回はabi、bytecodeを個別で出力してconfigで設定しましたが、truffleのartifactの形式で指定するなど色々な方法があるようですので、少しづつキャッチアップしていきたいと思います。


## TRUFFLE SUITE

TRUFFLE SUITEは「Truffle」「Ganache」「Drizzle（チョコソース垂らすやつ？）」ときてるので、基本的にチョコレート関連なのでしょうか。
「Praline」「Rocher」とか今後ありそうな無さそうな‥


## さいごに

ZEROBILLBANKでは一緒に働く仲間を募集中です。
[ZEROBILLBANK JAPAN Inc.](https://zerobillbank.com/%e3%81%8a%e5%95%8f%e3%81%84%e5%90%88%e3%82%8f%e3%81%9b/)