truffle-config.js の最適化オプション runs の検証と Truffle v4 への懸念

## はじめに

**Truffle** 環境の設定をする **truffle-config.js** ですが、最適化ブロックで **runs** という指定が使えるようです。

はて、この値はなんなんでしょう？

**Solidity** の[公式ページの説明](https://solidity-jp.readthedocs.io/ja/latest/using-the-compiler.html)からするに、**solc** においての **runs** は下記のようなものらしいです。

```
デフォルトでは、オプティマイザは、コントラクトがそのライフタイムにわたって200回呼び出されると仮定して、コントラクトを最適化します。 イニシャルコントラクトのデプロイのガス代をより安く、その後の関数実行時のガス代をより高くしたい場合は、--optimize-runs=1 と設定してください。 多くのトランザクションを想定していて、デフォルトより高いデプロイコストとアウトプットサイズを気にしないのであれば、 --optimize-runs を大きな数に設定してください。
```

どうやら **runs** の設定は、**「値を小さくするとデプロイ時のガス代が下がって トランザクションのガス代が上がる、値を大きくするとデプロイ時のガス代が上がって トランザクションのガス代が下がる」**とのこと。

**Truffle** 環境ではコンパイラとして **solcjs** が使われているらしいので **solc** の機能がそのまま使えるとは限らないのですが、物は試しです。どのような効果があるのかテストしてみましょう（※こちらが[テストプロジェクト](https://github.com/hakumai-iida/SolcOptimizeTest)となります）。

## テスト方法

ガス消費の計測用に、構造体を１つ作成して動的配列に追加する関数を持つ、シンプルなコントラクトを準備します（※せっかくなので [CryptoKitties](https://etherscan.io/address/0x06012c8cf97bead5deae237070f9587f8e7a266d#code) の **Kitty** 構造体を拝借しました）。

<details><summary>テストコード（クリックで開閉します）</summary><div>

```
pragma solidity 0.5.12;

contract CompileOpt5{
  // クリプトキティの構造体を拝借
  struct Kitty {
    uint256 genes;
    uint64 birthTime;
    uint64 cooldownEndBlock;
    uint32 matronId;
    uint32 sireId;
    uint32 siringWithId;
    uint16 cooldownIndex;
    uint16 generation;
  }

  // ニャンコの配列
  Kitty[] internal kitties;

  // 要素数の取得
  function getTotalKitties() public view returns( uint256 ){
    return kitties.length;
  }

  // ニャンコの追加
  function createKitty( uint256 _val256 ) public{
    Kitty memory _kitty = Kitty({
      genes: _val256,
      birthTime: uint64(_val256),
      cooldownEndBlock: uint64(_val256),
      matronId: uint32(_val256),
      sireId: uint32(_val256),
      siringWithId: uint32(_val256),
      cooldownIndex: uint16(_val256),
      generation: uint16(_val256)
    });

    kitties.push( _kitty );
  }
}
```
</div></details>

上記コードを、**runs** の値が **[1]**、**[200]**、**[2000]** の設定でコンパイルしたコントラクトを、それぞれデプロイした際のガス消費量と、関数にアクセスした際のガス消費量で比較します。ついでに、最適化を無効にした状態もテストしてみましょう。

## Truffle v5.1.2 (solc-js v0.5.12) でのテスト

各コントラクトをデプロイし、**createKitty** 関数へ２００回以上アクセスをしてみました（※ひょっとしたら **runs** で指定した回数のトランザクションを超えるとコストが高くなったりするのかと思ったので多めにテスト）。
で、結果としては、すべてのコントラクトにおいて初回トランザクションだけガス消費が低く、２回目以降は一律の消費という結果になりました。

・最適化 **有り**(**runs=1**) 
　[デプロイ時のガス消費](https://rinkeby.etherscan.io/tx/0xe5b6766964ca232d6a979dca993ed87554e52dcfa001368b25a3d0d70728248c)： **187,711**
　[初回のガス消費](https://rinkeby.etherscan.io/tx/0x63b021cf6b481a66f67c0f8a231de34e665706ed0236230a2e16af772b4b4206)： **45,551**
　[２回目以降のガス消費](https://rinkeby.etherscan.io/tx/0x3f967c7e2cbc269b88d71ccd39d73ed0e340253f5a652a95bb75226861577424)： **68,963**
<details><summary>truffle-config.js（クリックで開閉します）</summary><div>

```
module.exports = {
  // 〜 省略 〜

  // 最適化有り：runs=1
  compilers: {
    solc: {
      version: "0.5.12",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: true,
          runs: 1
        }
      }
    }
  }
}
```
</div></details>


・最適化 **有り**(**runs=200**)
　[デプロイ時のガス消費](https://rinkeby.etherscan.io/tx/0x83afb9786846b2654b8e54ac95ff6978a6932939d63a85f502967aeca745edea)： **190,327**
　[初回のガス消費](https://rinkeby.etherscan.io/tx/0x168c2e19afa1c225865a6b60b981e4475924f1e6e51f3a6fe60c8fa789967c15)： **45,503**
　[２回目以降のガス消費](https://rinkeby.etherscan.io/tx/0x7c0a2e7b9db85ba0c1fc0e37c763674aefee90a2ce1745fde4ec09bf8dc4028a)： **68,915**
<details><summary>truffle-config.js（クリックで開閉します）</summary><div>

```
module.exports = {
  // 〜 省略 〜

  // 最適化有り：runs=200（デフォルト値）
  compilers: {
    solc: {
      version: "0.5.12",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: true,
          runs: 200
        }
      }
    }
  }
}
```
</div></details>


・最適化 **有り**(**runs=2000**)
　[デプロイ時のガス消費](https://rinkeby.etherscan.io/tx/0x7258c7380ee30555eb8f2b74bd3e2b51d103d5d993ac5df8e429191588028f54)：**245,294**
　[初回のガス消費](https://rinkeby.etherscan.io/tx/0xc84e2c104f6b83e4642cbf355bdbd1b56ceb66e20b37afee534dc729f2d7498a)： **45,419**
　[２回目以降のガス消費](https://rinkeby.etherscan.io/tx/0xf55ccc017d60a13080adf8f3dc4bcfd80a02bba02723369dfdb4cda37b5db25d)：  **68,831**
<details><summary>truffle-config.js（クリックで開閉します）</summary><div>

```
module.exports = {
  // 〜 省略 〜

  // 最適化有り：runs=2000
  compilers: {
    solc: {
      version: "0.5.12",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: true,
          runs: 2000
        }
      }
    }
  }
}
```
</div></details>

・最適化 **無し**
　[デプロイ時のガス消費](https://rinkeby.etherscan.io/tx/0x0e44f38ec570a2625cfbd6c0f97847a4ee852b398317ea4088aa5058f0208bcb)： **220,118**
　[初回のガス消費](https://rinkeby.etherscan.io/tx/0x6d602c079a0a435fba6c365d8d01b55e1220ecd9f84d6db74f8cb37cb8b8a1e3)： **55,710**
　[２回目以降のガス消費](https://rinkeby.etherscan.io/tx/0x1e78e1e1fb75e0784b2ce41e3fe85958414b5c37fb3fe9bf38a2f27eb5291246)： **79,122**
<details><summary>truffle-config.js（クリックで開閉します）</summary><div>

```
module.exports = {
  // 〜 省略 〜

  // 最適化無し
  compilers: {
    solc: {
      version: "0.5.12",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: false
        }
      }
    }
  }
}
```
</div></details>

さて、各値を比較してみるに、**runs** の値が小さいほどデプロイ時のガス消費が抑えられているようです。
一方で、**runs** の値による、トランザクションのコスト変動は微々たるものです。デフォルトの **runs=200** に対して、トランザクションを犠牲するはずの **runs=1** でも、 優遇されるはずの **runs=2000** においても、恩恵を実感できるほどの差が出ませんでした。

また、最適化無しの状態にくらべると、２回目以降のトランザクションのガス消費が**１３％**ほど改善されています。

## まとめ

**truffle-config.js** において **runs** の値は、特に気にしなくても良さそうです。とりあえず最適化しておくだけで、ガス消費も多少収まってくれるみたいですし、**DApp** のリリース時に最適化し忘れさえしなければ問題はなさそうですね。

## Truffle v4 での最適化はどうなるのか？
さて、ある意味ここからが本題かもしれません。
最適化の指定は **Truffle v4** 環境でもちゃんと効果があるのでしょうか？

**Truffle** 関連の情報、特に書籍等では、**v4** 系の内容を取り扱ったものが多い印象があります。また、**truffle console** の使い勝手が、**v4** と **v5** で結構変わっているので、あえて **v4** 環境を使い続けている方も少なくないと思います。折角なので、**v4** 系でもテストしてみましょう。

## Truffle v4.1.15 (solcjs v0.4.25) でのテスト

では、**v4** 向けに名前とコンパイラのバージョンを調整したコントラクトを用意して、テストの開始です。

<details><summary>テストコード（クリックで開閉します）</summary><div>

```
pragma solidity 0.4.25;

contract CompileOpt4{
  // クリプトキティの構造体を拝借
  struct Kitty {
    uint256 genes;
    uint64 birthTime;
    uint64 cooldownEndBlock;
    uint32 matronId;
    uint32 sireId;
    uint32 siringWithId;
    uint16 cooldownIndex;
    uint16 generation;
  }

  // ニャンコの配列
  Kitty[] internal kitties;

  // 要素数の取得
  function getTotalKitties() public view returns( uint256 ){
    return kitties.length;
  }

  // ニャンコの追加
  function createKitty( uint256 _val256 ) public{
    Kitty memory _kitty = Kitty({
      genes: _val256,
      birthTime: uint64(_val256),
      cooldownEndBlock: uint64(_val256),
      matronId: uint32(_val256),
      sireId: uint32(_val256),
      siringWithId: uint32(_val256),
      cooldownIndex: uint16(_val256),
      generation: uint16(_val256)
    });

    kitties.push( _kitty );
  }
}
```
</div></details>


・最適化 **有り**(**runs=1**) 
　[デプロイ時のガス消費](https://rinkeby.etherscan.io/tx/0x55895b38955f9d52ba4716bdb300f3d5bbf00656f90f7db30af03e4f59ca3588)：**225,602**
　[初回のガス消費](https://rinkeby.etherscan.io/tx/0x7a4275e0e7a630439836dd73e7f92bab3258fdfd8fc39565bd2f12769013f4f9)： **55,701**
　[２回目以降のガス消費](https://rinkeby.etherscan.io/tx/0x750f4bb12ebd174bebe0a68f94d3a2bb6f47d238c3e959d5beaad5ec5719a19c)： **79,113** 
<details><summary>truffle-config.js（クリックで開閉します）</summary><div>

```
module.exports = {
  // 〜 省略 〜

  // 最適化有り：runs=1
  compilers: {
    solc: {
      version: "0.4.25",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: true,
          runs: 1
        }
      }
    }
  }
}
```
</div></details>

・最適化 **有り**(**runs=200**)
　[デプロイ時のガス消費](https://rinkeby.etherscan.io/tx/0xb504e846ed2f15b871c1927c37e1e8aeb246a6b5928d39793f3f1046888207a2)：**225,602**
　[初回のガス消費](https://rinkeby.etherscan.io/tx/0x9ffe84545c1f6c0c8e91fe313b4b69f50ab63d1f5309bd5548770dc1008b59c7)： **55,701**
　[２回目以降のガス消費](https://rinkeby.etherscan.io/tx/0xa940f0cf176504d8cf44d623983e83772ebb9dc62facc1ef4e44a66a09cc9544)： **79,113** 
<details><summary>truffle-config.js（クリックで開閉します）</summary><div>

```
module.exports = {
  // 〜 省略 〜

  // 最適化有り：runs=200（デフォルト値）
  compilers: {
    solc: {
      version: "0.4.25",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: true,
          runs: 200
        }
      }
    }
  }
}
```
</div></details>

・最適化 **有り**(**runs=2000**)
　[デプロイ時のガス消費](https://rinkeby.etherscan.io/tx/0x65f36616c9eaf24a885fd27b5bf7b52c9191100301b1eb7a2216a5a16bdb8213)： **225,602**
　[初回のガス消費](https://rinkeby.etherscan.io/tx/0xd72441d5cf4bde2c563e6c1654d982df9b4a3d0a8f539d2234ff5020de6fb5d0)： **55,701**
　[２回目以降のガス消費](https://rinkeby.etherscan.io/tx/0xf0a505445cc589e7f35aad24bed88ae6b0fc0ae878b84759aa2214aa01440882)： **79,113**
<details><summary>truffle-config.js（クリックで開閉します）</summary><div>

```
module.exports = {
  // 〜 省略 〜

  // 最適化有り：runs=2000
  compilers: {
    solc: {
      version: "0.4.25",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: true,
          runs: 2000
        }
      }
    }
  }
}
```
</div></details>

・最適化 **無し**
　[デプロイ時のガス消費](https://rinkeby.etherscan.io/tx/0x8ec502296a580a7fa5e734aed7e26d2caba4e0c740a9d830b450294d7ee0d661)：**225,602**
　[初回のガス消費](https://rinkeby.etherscan.io/tx/0xcddb4ffd7b4bcd3728408cf3da9a65fba906b53403f1c1d93de9c82df6d2b247)： **55,701**
　[２回目以降のガス消費](https://rinkeby.etherscan.io/tx/0xe30311b9e0bd93b81478b894a3d64ee906b6d6af993993cb0ab583ec4cf66bf6)： **79,113**
<details><summary>truffle-config.js（クリックで開閉します）</summary><div>

```
module.exports = {
  // 〜 省略 〜

  // 最適化無し
  compilers: {
    solc: {
      version: "0.4.25",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: false
        }
      }
    }
  }
}
```
</div></details>

やや、すべての設定において、ガス消費量が完全に同じ値となりました…。

最初はコンパイル指定をミスったと思ったのですが、イーサスキャンでバイトコードを眺めてみると、各コントラクトともバイナリレベルで若干の差が出ています。仮に、同じバイナリデータであれば、イーサスキャンで表示した際、重複コントラクト扱いされるはずなので、個別に表示できている時点でコントラクト間で差分があるのは間違いなさそうです。

それなのに、イーサスキャン上でベリファイコードを登録しようとすると、「Optimization Enabled: **No**」でないと受け付けてくれません。

まとめると、最適化の設定によりバイナリデータに差が出ているにも関わらず、イーサスキャン上では最適化が認識されていないようなのです。

そして最大の問題が**「最適化有りでも無しでも、すべてのコントラクトのガス消費量が同じになっている」**という事実。

この結果が、私の環境や手順の間違いによるものであればよいのですが、仮に、 **Truffle v4** 環境が最適化の指定をコントラクトに反映してくれないのであれば、由々しき事態です。

もし **Truffle v4** をお使いで **Dapp** のリリースを念頭に置いている方がいらっしゃるのであれば、お早めに、お使いの環境で最適化が機能するかテストしてみることをお勧めします。リリース間際になって「最適化が効かない！？」なんて状況は考えるだに恐ろしいです。