Solidityでガス代がかかりやすい処理のまとめ

## はじめに
Solidityで記述されたスマートコントラクトに対して、非view, 非pure な関数[^1] を実行した際にかかる主要な演算コスト（ガス代）をまとめました。

各項目ではRemix VM上で実行した際の transaction cost の実測値を記載しています。単位は[gas]ですが、以下では省略します。
Solidityのバージョンは `0.8.7` です。

### 参考資料
https://github.com/wolflo/evm-opcodes
[Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf)


## 基本コスト
関数（トランザクション）を実行する際、 `21000` の基本コスト がかかります。そこそこ大きいので、軽量な処理であればこれがボトルネックになります。
```
contract MyContract  {
    function DoNothing() external {}
}
```
> transaction cost: 21186

上の例では基本コストに加えて、前後処理にかかる諸々のEVM上のオペレーションコストが発生していますが、量としては無視できる程度でしょう。

## 状態変数（ストレージ）への書き込み
状態変数の書き込みは基本的にコストがかかりやすいです。詳細な計算式は[こちら](https://github.com/wolflo/evm-opcodes/blob/main/gas.md#a7-sstore)を参照して下さい。

要点はこのあたりです。
- 0の変数に0以外をセットする (`+20000`)
- 非0の変数に異なる値に変更する (`+2900`)
  - 同じトランザクションで変更されていない変数を変更する (`+2100`)

### 例: 0の変数に非0の値をセットする
元々0の状態変数に0以外の値をセットする際には *SSTORE* オペレーションが実行され、`20000` のコストがかかります。
```
contract MyContract {
    int a;
    function SetInt() external {
        a = 5;
    }
}
```
> transaction cost: 43300

mappingの新たなkeyに値をセットする場合や、可変長配列の要素追加も同様です。
```
contract MyContract {
    mapping(int256 => int256) intToInt;
    function SetInt() external {
        intToInt[1] = 1;
    }
}
```
> transaction cost: 43378

### 例: 非0の変数を書き換える
こちらは非0の変数に異なる値に変更する (`+2900`) と同じトランザクションで変更されていない変数を変更する (`+2100`) の組み合わせで合計 `5000` の演算コストがかかっています。
```
contract MyContract {
    int a = 5;
    function SetInt() external {
        a = 6;
    }
}
```
> transaction cost: 26200

## 状態変数（ストレージ）の読み込み
トランザクション内で初めてアクセスする状態変数を読み込む際には大きなコストがかかります。
- 未アクセスの状態変数の読み込み: `2100`
- 一度アクセスした状態変数の読み込み: `100`

残念ながら同じstructの中の各変数や、同じ配列やmapping中の各要素も、それぞれ別の状態変数扱いになります。
従って、大きな状態変数の配列を for loop で巡回するだけでも、（初回は）大きなコストがかかります。

### 例: （状態変数の）大きな配列やマップの各要素にアクセスする
下記の例ではサイズ100の配列を総なめしているので `2100` + 100 = `210000` のコストがかかっています。
```
contract MyContract {
    int[100] a;

    function MyFunc() external {
        bool b;
        for(uint i = 0; i < 100; i++) {
            b = a[i] > 0;
        }
    }
}
```
> 253431



## Contractのインスタンスを作成する
Contractをインスタンス化する際、以下のコストがかかります
- *Create* オペレーション: `32000`
- memory expansionコスト: メモリ量依存 (下のケースだと 10000弱くらい)
- code deposit コスト: 該当Contractのコード長依存（**ボトルネックになりやすいので要注意**）

### 例: 空のコントラクトを生成する
```
contract MyContract {
    function CreateContract() external {
        new SubContract();
    }
}

contract SubContract {}
```
> transaction cost: 65978

### 例: 関数の定義を含むコントラクトを生成する
適当な関数を一つ定義するだけで code deposit コストが 43881 も増えていることが分かります。まさにOOPの敵です。
SubContractがContractを継承している場合は、当然ながら継承元のContractの分も加算されます。
```
contract MyContract {
    function CreateContract() external {
        new SubContract();
    }
}

contract SubContract {
    function Func1() public {
        int a = 5;
        a = 6;
        a = 7;
        for(int i = 0; i < a; i++ ) {
            int b = a;
        }
    }
}
```
> transaction cost: 109859

### 例: 関数の定義を含むコントラクトを複数生成する
基本コストを除いて比較すると、残念ながら同じコントラクトを一つ生成した場合のコストのほぼ2倍[^2]です。SubContractのコード長分のコストも毎回同じだけかかります。
```
contract MyContract {
    function CreateContract() external {
        new SubContract();
        new SubContract();
    }
}

contract SubContract {
    function Func1() public {
        int a = 5;
        a = 6;
        a = 7;
        for(int i = 0; i < a; i++ ) {
            int b = a;
        }
    }
}
```
> transaction cost: 198499

### 例: インスタンス化したContractの値をセットする
本体のContract (MyContract) の状態変数を変更していなくても、生成したコントラクトの状態変数を変更する場合は、ストレージへの書き込みコストが発生します。

```
contract MyContract {
    function CreateContract() external {
        new SubContract();
    }
}

contract SubContract {
    int a = 5;
    int b = 6;
    int c = 7;
}
```
> transaction cost: 132302

こちらは比較用です。状態変数が初期値 (=0) のままなら書き込みコストも発生していません。

```
contract MyContract {
    function CreateContract() external {
        new SubContract();
    }
}

contract SubContract {
    int a;
    int b;
    int c;
}
```
> transaction cost: 65978

[^1]: view, pure 修飾されている関数（=状態変数を変更しない関数）を外部から呼び出す場合は一切のガス代がかかりません。
[^2]: 1.9975倍