# [Bunzz Decipher] 『Multicall2』コントラクトを理解しよう！

> Multicall2コントラクトは、MakerDAOが開発した便利なツールで、Ethereumブロックチェーン上で複数の関数呼び出しを効率的に実行します。

- URL: https://cardene.dev/articles/0b08b59bd5ca99
- 公開日: 2023-09-06
- 更新日: 2023-09-06
- トピック: Blockchain

# はじめに

初めまして。
**CryptoGames**というブロックチェーンゲーム企業でエンジニアをしている **cardene（かるでね）** です！
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

https://cryptogames.co.jp/

代表的なゲームは**クリプトスペルズ**というブロックチェーンゲームです。

https://cryptospells.jp/

今回はBunzzの新機能『**DeCipher**』を使用して、「**Multicall2**」コントラクトを見てみようと思います。

『**DeCipher**』はAIを使用してコントラクトのドキュメントを自動生成してくれるサービスです。

https://www.bunzz.dev/decipher

詳しい使い方に関しては以下の記事を参考にしてください！

https://zenn.dev/heku/articles/33266f0c19d523

:::message
この記事はあくまで個人が「**DeCipher**」を使用してみての記事になります。
間違った部分などもあるかと思いますが、その際はコメントなどで教えてください。
:::

今回使用する『**DeCipher**』のリンクは以下になります。

https://app.bunzz.dev/decipher/chains/1/addresses/0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696

Etherscanのリンクは以下になります。

https://etherscan.io/address/0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696

# 概要

**Multicall2**コントラクトは、**MakerDAO**が開発した便利なツールで、Ethereumブロックチェーン上で複数の関数呼び出しを効率的に実行します。
元々の**Multicall**コントラクトの機能を広げ、バージョン2としてさらに使いやすくしたものです。

## 目的
**Multicall2**コントラクトの目的は、複数の関数呼び出しの結果を集めることです。
これは、Ethereumブロックチェーン上で複数のコントラクトや関数からデータを取得する必要がある場合に便利です。
通常、1つずつ関数を呼び出すよりも、**Multicall2**コントラクトを使ってまとめて実行するほうが時間とコストを節約できます。

## 責務

### まとめて関数呼び出し
コントラクト内の「`aggregate`」という関数は、複数の関数呼び出しを受け取ることができます。
各呼び出しの対象アドレスとデータが`Call`構造体に格納され、一度に実行されます。
エラーが発生した場合はリバートします。

### ブロック情報の取得
コントラクトは、現在のブロックに関する情報を取得するための関数を提供しています。
ブロック番号やタイムスタンプなどの情報を取得できます。

### ETH残高の取得
指定されたアドレスの**Ether**残高を取得するための関数も提供されています。

### トライ関数呼び出し
「`tryAggregate`」という関数は、複数の関数呼び出しをまとめて実行しますが、必要に応じてエラーハンドリングの方法を選ぶことができます。

### ブロックと関数呼び出しのトライ
「`tryBlockAndAggregate`」という関数は、関数呼び出しとブロック情報の取得を組み合わせたもので、ブロック番号やタイムスタンプなどを取得できます。

# 使い方

**Multicall2**コントラクトは、複数関数呼び出しの結果を1つにまとめるためのものです。
1つのトランザクションで複数のコントラクトや関数からデータを取得することができ、ネットワークリクエストの数を減らし、効率的にデータを取得できます。

以下は、**Multicall2**コントラクトを使い時の手順です。

### 1. 呼び出しの定義
最初に、まとめて実行する呼び出しデータを定義します。
それぞれの呼び出しは`Call`構造体で表され、対象アドレスと`callData`バイト列を含みます。`callData`には、呼び出す関数のシグネチャとパラメータがエンコードされています。

### 2. aggregate関数の呼び出し
定義した呼び出しを配列として、`aggregate`関数に渡します。
この関数は、各呼び出しを順番に実行し、戻りデータを配列に格納します。
どれかの呼び出しが失敗すると、トランザクション全体が元に戻ります。

:::message
**関数のシグネチャ**
関数の識別情報を表すもので、関数の名前と引数のデータ型・順序、戻り値のデータ型を含みます。
シグネチャは、関数が呼び出される際に、正しい引数の数や型が提供されているかどうかを判断するために使用されます。

具体的には、関数のシグネチャは以下の要素を含みます。

- 関数名
- 引数のデータ型と順序
	- 関数が受け取る引数のデータ型とその順序がシグネチャに含まれます。
- 戻り値のデータ型
	- 関数が戻す値のデータ型がシグネチャに含まれます。
:::

### 3. 戻りデータの処理
`aggregate`関数は、実行時のブロック番号と、各呼び出しの戻りデータをバイト列の配列で返します。
このデータを、呼び出した関数の戻り型に合わせてデコードする必要があります。

### 4. tryAggregate関数の利用（オプション）
呼び出しを実行したいが、1つの呼び出しが失敗してもトランザクション全体を失敗させたくない場合は、`tryAggregate`関数を使います。
この関数は`aggregate`と似ていますが、失敗してもリバートされず、呼び出し毎に`Result`構造体を返します。
この構造体には、成功フラグと戻りデータが含まれます。

### 5. tryBlockAndAggregate関数の利用（オプション）
実行時のブロック番号とブロックハッシュも取得したい場合は、`tryBlockAndAggregate`関数を使います。
この関数は`tryAggregate`と同様に実行時のブロック番号とブロックハッシュも返します。

# パラメーター

なし。

# コントラクト

## Multicall2

### Call

:::details Call
```solidity
struct Call {
    address target;
    bytes callData;
}
```
:::

**概要**
関数呼び出しの対象となるコントラクトアドレスと関数呼び出しのデータを保持する構造体。

**パラメーター**
- `target`
	- 関数を呼び出す対象のコントラクトアドレス。
- `callData`
	- 呼び出す関数のシグネチャと引数をエンコードしたバイトデータ。
	- 関数呼び出しの詳細な情報が含まれています。

---

### Result

:::details Result
```solidity
struct Result {
    bool success;
    bytes returnData;
}
```
:::

**概要**
関数呼び出しの結果を示す情報を保持する構造体。
成功したかどうかと戻りデータを格納します。

**パラメーター**
- `success`
	- 関数呼び出しが成功したかどうかを示す真偽値。
	- 成功した場合は`true`、失敗した場合は`false`が格納されます。
- `returnData`
	- 関数呼び出しの結果として返されたデータを格納するバイトデータ。
	- 関数の戻り値が含まれます。

---

### aggregate

:::details aggregate
```solidity
function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) {
    blockNumber = block.number;
    returnData = new bytes[](calls.length);
    for(uint256 i = 0; i < calls.length; i++) {
        (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
        require(success, "Multicall aggregate: call failed");
        returnData[i] = ret;
    }
}
```
:::

**概要**
複数の外部呼び出しをまとめて行い結果を返す関数。

**詳細**
- `calls`という配列に含まれる複数の`Call`オブジェクトを受け取ります。
- 現在のブロック番号を`blockNumber`に保存します。
- `returnData`の初期化を行います。
	- `calls`の数だけ要素を持つバイト列の配列です。
- 各`Call`オブジェクトを順番に実行し、その結果を`returnData`に格納します。
	- 呼び出しに失敗した場合は例外が発生します。

**引数**
- `calls`
	- 外部呼び出しを行うための`Call`オブジェクトの配列。

**戻り値**
- `blockNumber`
	- 現在のブロック番号。
- `returnData`
	- ≥各外部呼び出しの結果を格納したバイト列の配列。

---

### blockAndAggregate

:::details blockAndAggregate
```solidity
function blockAndAggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
    (blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls);
}
```
:::

**概要**
`tryBlockAndAggregate`関数を呼び出して、ブロック情報と外部呼び出しの結果をまとめて取得する関数。

**詳細**
- `tryBlockAndAggregate`関数を呼び出して、ブロック情報と外部呼び出しの結果を取得します。
- `tryBlockAndAggregate`関数の呼び出し結果が`blockNumber`、`blockHash`、および`returnData`に代入されます。

**引数**
- `calls`
	- 外部呼び出しを行うための`Call`オブジェクトの配列。

**戻り値**
- `blockNumber`
	- 現在のブロック番号。
- `blockHash`
	- 現在のブロックのハッシュ値。
- `returnData`
	- 各外部呼び出しの結果を格納した`Result`オブジェクトの配列。

---

### getBlockHash

:::details getBlockHash
```solidity
function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
    blockHash = blockhash(blockNumber);
}
```
:::

**概要**
指定されたブロック番号のブロックハッシュ値を取得する関数。

**詳細**
- 指定された`blockNumber`のブロックハッシュ値を`blockHash`に代入して返します。

**引数**
- `blockNumber`
	- 取得したいブロックの番号。

**戻り値**
- `blockHash`
	- 指定されたブロックのハッシュ値。

---

### getBlockNumber

:::details getBlockNumber
```solidity
function getBlockNumber() public view returns (uint256 blockNumber) {
    blockNumber = block.number;
}
```
:::

**概要**
現在のブロック番号を取得する関数。

**詳細**
- 現在のブロック番号を`blockNumber`に代入して返します。

**戻り値**
- `blockNumber`
	- 現在のブロック番号。

---

### getCurrentBlockCoinbase

:::details getCurrentBlockCoinbase
```solidity
function getCurrentBlockCoinbase() public view returns (address coinbase) {
    coinbase = block.coinbase;
}
```
:::

**概要**
現在のブロックマイニング報酬を受け取るアドレスを取得する関数。

**詳細**
- `block.coinbase`は、現在のブロックをマイニングしたマイナーのアドレス、つまりコインベースアドレスを表します。
- 現在のブロックのコインベースアドレスを`coinbase`に代入して返します。

**戻り値**
- `coinbase`
	- 現在のブロックのコインベースアドレス。

---

### getCurrentBlockDifficulty

:::details getCurrentBlockDifficulty
```solidity
function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
    difficulty = block.difficulty;
}
```
:::

**概要**
現在のブロックの難易度を取得する関数。

**詳細**
- 現在のブロックのマイニング難易度を`difficulty`に代入して返します。

**戻り値**
- `difficulty`
	- 現在のブロックの難易度。

---

### getCurrentBlockGasLimit

:::details getCurrentBlockGasLimit
```solidity
function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
    gaslimit = block.gaslimit;
}
```
:::

**概要**
現在のブロックのガスリミットを取得する関数。

**詳細**
- 現在のブロックのガスリミットを`gaslimit`に代入して返します。

**戻り値**
- `gaslimit`
	- 現在のブロックのガスリミット。

---

### getCurrentBlockTimestamp

:::details getCurrentBlockTimestamp
```solidity
function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
    timestamp = block.timestamp;
}
```
:::

**概要**
現在のブロックのタイムスタンプを取得する関数。

**詳細**
- 現在のブロックのタイムスタンプを`timestamp`に代入して返します。

**戻り値**
- `timestamp`
	- 現在のブロックのタイムスタンプ。

---

### getEthBalance

:::details getEthBalance
```solidity
function getEthBalance(address addr) public view returns (uint256 balance) {
    balance = addr.balance;
}
```
:::

**概要**
指定されたアドレスの**ETH**残高を取得する関数。

**詳細**
- 指定されたアドレスの**ETH**残高を`balance`に代入して返します。

**引数**
- `addr`
	- **ETH**残高を取得したいアドレス。

**戻り値**
- `balance`
	- 指定されたアドレスの**ETH**残高。

---

### getLastBlockHash

:::details getLastBlockHash
```solidity
function getLastBlockHash() public view returns (bytes32 blockHash) {
    blockHash = blockhash(block.number - 1);
}
```
:::

**概要**
直前のブロックのハッシュ値を取得する関数。

**詳細**
- 直前のブロックのハッシュ値を`blockHash`に代入して返します。

**戻り値**
- `blockHash`
	- 直前のブロックのハッシュ値。

---

### tryAggregate

:::details tryAggregate
```solidity
function tryAggregate(bool requireSuccess, Call[] memory calls) public returns (Result[] memory returnData) {
    returnData = new Result[](calls.length);
    for(uint256 i = 0; i < calls.length; i++) {
        (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);

        if (requireSuccess) {
            require(success, "Multicall2 aggregate: call failed");
        }

        returnData[i] = Result(success, ret);
    }
}
```
:::

**概要**
指定された外部呼び出しを順番に実行し、結果を収集する関数。
必要に応じて呼び出しの成功を確認し、結果を`Result`オブジェクトの配列として返します。

**詳細**
- `requireSuccess`が`true`の場合、各呼び出しの成功を確認し、失敗した場合に例外が発生します。
- `calls`という配列に含まれる複数の`Call`オブジェクトを受け取ります。
- 各`Call`オブジェクトを順番に実行し、その結果と成功フラグを`Result`オブジェクトに格納します。

**引数**
- `requireSuccess`
	- 呼び出しの成功を確認するかどうかのフラグ。
- `calls`
	- 外部呼び出しを行うための`Call`オブジェクトの配列。

**戻り値**
- `returnData`
	- 各外部呼び出しの結果と成功フラグを格納した`Result`オブジェクトの配列。

---

### tryBlockAndAggregate

:::details tryBlockAndAggregate
```solidity
function tryBlockAndAggregate(bool requireSuccess, Call[] memory calls) public returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
    blockNumber = block.number;
    blockHash = blockhash(block.number);
    returnData = tryAggregate(requireSuccess, calls);
}
```
:::

**概要**
指定された外部呼び出しとブロック情報を組み合わせて実行し、結果を収集する関数。
必要に応じて呼び出しの成功を確認し、結果を`Result`オブジェクトの配列として返します。

**詳細**
- `requireSuccess`が`true`の場合、各呼び出しの成功を確認し、失敗した場合に例外が発生します。
- 現在のブロック番号を`blockNumber`に保存します。
- 現在のブロックのハッシュ値を`blockHash`に保存します。
- 外部呼び出しを行い、その結果と成功フラグを`Result`オブジェクトに格納します。

**引数**
- `requireSuccess`
	- 呼び出しの成功を確認するかどうかのフラグ。
- `calls`
	- 外部呼び出しを行うための`Call`オブジェクトの配列。

**戻り値**
- `blockNumber`
	- 現在のブロック番号。
- `blockHash`
	- 現在のブロックのハッシュ値。
- `returnData`
	- 各外部呼び出しの結果と成功フラグを格納した`Result`オブジェクトの配列。

---

# コード

## Multicall2.sol

:::details Multicall2.sol
```solidity
/**
 *Submitted for verification at Etherscan.io on 2021-03-23
*/

pragma solidity >=0.5.0;
pragma experimental ABIEncoderV2;

/// @title Multicall2 - Aggregate results from multiple read-only function calls
/// @author Michael Elliot <mike@makerdao.com>
/// @author Joshua Levine <joshua@makerdao.com>
/// @author Nick Johnson <arachnid@notdot.net>

contract Multicall2 {
    struct Call {
        address target;
        bytes callData;
    }
    struct Result {
        bool success;
        bytes returnData;
    }

    function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) {
        blockNumber = block.number;
        returnData = new bytes[](calls.length);
        for(uint256 i = 0; i < calls.length; i++) {
            (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
            require(success, "Multicall aggregate: call failed");
            returnData[i] = ret;
        }
    }
    function blockAndAggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
        (blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls);
    }
    function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
        blockHash = blockhash(blockNumber);
    }
    function getBlockNumber() public view returns (uint256 blockNumber) {
        blockNumber = block.number;
    }
    function getCurrentBlockCoinbase() public view returns (address coinbase) {
        coinbase = block.coinbase;
    }
    function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
        difficulty = block.difficulty;
    }
    function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
        gaslimit = block.gaslimit;
    }
    function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
        timestamp = block.timestamp;
    }
    function getEthBalance(address addr) public view returns (uint256 balance) {
        balance = addr.balance;
    }
    function getLastBlockHash() public view returns (bytes32 blockHash) {
        blockHash = blockhash(block.number - 1);
    }
    function tryAggregate(bool requireSuccess, Call[] memory calls) public returns (Result[] memory returnData) {
        returnData = new Result[](calls.length);
        for(uint256 i = 0; i < calls.length; i++) {
            (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);

            if (requireSuccess) {
                require(success, "Multicall2 aggregate: call failed");
            }

            returnData[i] = Result(success, ret);
        }
    }
    function tryBlockAndAggregate(bool requireSuccess, Call[] memory calls) public returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
        blockNumber = block.number;
        blockHash = blockhash(block.number);
        returnData = tryAggregate(requireSuccess, calls);
    }
}
```
:::

# 最後に

今回の記事では、Bunzzの新機能『**DeCipher**』を使用して、「**Multicall2**」コントラクトを見てきました。
いかがだったでしょうか？
今後も特定のNFTやコントラクトをピックアップしてまとめて行きたいと思います。

普段はブログやQiitaでブロックチェーンやAIに関する記事を挙げているので、よければ見ていってください！

https://chaldene.net/

https://qiita.com/cardene
