[Bunzz Decipher] 『Multicall2』コントラクトを理解しよう!
はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
今回はBunzzの新機能『DeCipher』を使用して、「Multicall2」コントラクトを見てみようと思います。
『DeCipher』はAIを使用してコントラクトのドキュメントを自動生成してくれるサービスです。
詳しい使い方に関しては以下の記事を参考にしてください!
今回使用する『DeCipher』のリンクは以下になります。
Etherscanのリンクは以下になります。
概要
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
関数に渡します。
この関数は、各呼び出しを順番に実行し、戻りデータを配列に格納します。
どれかの呼び出しが失敗すると、トランザクション全体が元に戻ります。
3. 戻りデータの処理
aggregate
関数は、実行時のブロック番号と、各呼び出しの戻りデータをバイト列の配列で返します。
このデータを、呼び出した関数の戻り型に合わせてデコードする必要があります。
4. tryAggregate関数の利用(オプション)
呼び出しを実行したいが、1つの呼び出しが失敗してもトランザクション全体を失敗させたくない場合は、tryAggregate
関数を使います。
この関数はaggregate
と似ていますが、失敗してもリバートされず、呼び出し毎にResult
構造体を返します。
この構造体には、成功フラグと戻りデータが含まれます。
5. tryBlockAndAggregate関数の利用(オプション)
実行時のブロック番号とブロックハッシュも取得したい場合は、tryBlockAndAggregate
関数を使います。
この関数はtryAggregate
と同様に実行時のブロック番号とブロックハッシュも返します。
パラメーター
なし。
コントラクト
Multicall2
Call
Call
struct Call {
address target;
bytes callData;
}
概要
関数呼び出しの対象となるコントラクトアドレスと関数呼び出しのデータを保持する構造体。
パラメーター
-
target
- 関数を呼び出す対象のコントラクトアドレス。
-
callData
- 呼び出す関数のシグネチャと引数をエンコードしたバイトデータ。
- 関数呼び出しの詳細な情報が含まれています。
Result
Result
struct Result {
bool success;
bytes returnData;
}
概要
関数呼び出しの結果を示す情報を保持する構造体。
成功したかどうかと戻りデータを格納します。
パラメーター
-
success
- 関数呼び出しが成功したかどうかを示す真偽値。
- 成功した場合は
true
、失敗した場合はfalse
が格納されます。
-
returnData
- 関数呼び出しの結果として返されたデータを格納するバイトデータ。
- 関数の戻り値が含まれます。
aggregate
aggregate
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
blockAndAggregate
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
getBlockHash
function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
blockHash = blockhash(blockNumber);
}
概要
指定されたブロック番号のブロックハッシュ値を取得する関数。
詳細
- 指定された
blockNumber
のブロックハッシュ値をblockHash
に代入して返します。
引数
-
blockNumber
- 取得したいブロックの番号。
戻り値
-
blockHash
- 指定されたブロックのハッシュ値。
getBlockNumber
getBlockNumber
function getBlockNumber() public view returns (uint256 blockNumber) {
blockNumber = block.number;
}
概要
現在のブロック番号を取得する関数。
詳細
- 現在のブロック番号を
blockNumber
に代入して返します。
戻り値
-
blockNumber
- 現在のブロック番号。
getCurrentBlockCoinbase
getCurrentBlockCoinbase
function getCurrentBlockCoinbase() public view returns (address coinbase) {
coinbase = block.coinbase;
}
概要
現在のブロックマイニング報酬を受け取るアドレスを取得する関数。
詳細
-
block.coinbase
は、現在のブロックをマイニングしたマイナーのアドレス、つまりコインベースアドレスを表します。 - 現在のブロックのコインベースアドレスを
coinbase
に代入して返します。
戻り値
-
coinbase
- 現在のブロックのコインベースアドレス。
getCurrentBlockDifficulty
getCurrentBlockDifficulty
function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
difficulty = block.difficulty;
}
概要
現在のブロックの難易度を取得する関数。
詳細
- 現在のブロックのマイニング難易度を
difficulty
に代入して返します。
戻り値
-
difficulty
- 現在のブロックの難易度。
getCurrentBlockGasLimit
getCurrentBlockGasLimit
function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
gaslimit = block.gaslimit;
}
概要
現在のブロックのガスリミットを取得する関数。
詳細
- 現在のブロックのガスリミットを
gaslimit
に代入して返します。
戻り値
-
gaslimit
- 現在のブロックのガスリミット。
getCurrentBlockTimestamp
getCurrentBlockTimestamp
function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
timestamp = block.timestamp;
}
概要
現在のブロックのタイムスタンプを取得する関数。
詳細
- 現在のブロックのタイムスタンプを
timestamp
に代入して返します。
戻り値
-
timestamp
- 現在のブロックのタイムスタンプ。
getEthBalance
getEthBalance
function getEthBalance(address addr) public view returns (uint256 balance) {
balance = addr.balance;
}
概要
指定されたアドレスのETH残高を取得する関数。
詳細
- 指定されたアドレスのETH残高を
balance
に代入して返します。
引数
-
addr
- ETH残高を取得したいアドレス。
戻り値
-
balance
- 指定されたアドレスのETH残高。
getLastBlockHash
getLastBlockHash
function getLastBlockHash() public view returns (bytes32 blockHash) {
blockHash = blockhash(block.number - 1);
}
概要
直前のブロックのハッシュ値を取得する関数。
詳細
- 直前のブロックのハッシュ値を
blockHash
に代入して返します。
戻り値
-
blockHash
- 直前のブロックのハッシュ値。
tryAggregate
tryAggregate
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
tryBlockAndAggregate
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
Multicall2.sol
/**
*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に関する記事を挙げているので、よければ見ていってください!
Discussion