はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
今回は、EOAアドレスが単一のトランザクションで、コントラクトの複数呼び出し機能を実行できる機能を提案している規格であるERC6357についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
6357は現在(2023年10月24日)では「Review」段階です。
他にも様々なERCについてまとめています。
概要
このEIPは、1つの機能、つまり「multicall
」を含む標準化されたインターフェースです。
これにより、外部所有者アカウント(EOA)は1つのトランザクションでスマートコントラクトの複数の関数を呼び出すことができます。
もし呼び出しの中でどれか1つでも失敗した場合、すべての呼び出しが取り消されます。
動機
現在、複数のERC721NFTを転送するには、転送するNFTの数と同じ回数のトランザクションを提出する必要があります。
これにより、ユーザーは各NFTの転送ごとに21000
ガス料金を支払わなければならないため、ユーザーの資金が無駄に使われてしまいます。
ERC721については以下を参考にしてください。
仕様
このEIPを実装するコントラクトは、以下のインターフェイスを実装する必要があります。
pragma solidity ^0.8.0;
interface IMulticall {
/// @notice Takes an array of abi-encoded call data, delegatecalls itself with each calldata, and returns the abi-encoded result
/// @dev Reverts if any delegatecall reverts
/// @param data The abi-encoded data
/// @returns results The abi-encoded return values
function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results);
/// @notice OPTIONAL. Takes an array of abi-encoded call data, delegatecalls itself with each calldata, and returns the abi-encoded result
/// @dev Reverts if any delegatecall reverts
/// @param data The abi-encoded data
/// @param values The effective msg.values. These must add up to at most msg.value
/// @returns results The abi-encoded return values
function multicallPayable(bytes[] calldata data, uint256[] values) external payable virtual returns (bytes[] memory results);
}
multicall
function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results)
概要
複数のabiエンコードされた呼び出しデータの配列を受け取り、それぞれのデータを使用して自身をデリゲートコールし、結果をabiエンコードして返す関数。
**delegatecall(デリゲートコール)**については以下を参考にしてください。
詳細
提供された呼び出しデータの配列に含まれる各データを順番に処理し、それぞれのデータを使用してコントラクト自体をデリゲートコールします。
各呼び出しの結果はabiエンコードされ、配列で返されます。
もしデリゲートコールの中で一つでもrevert
が発生した場合、この関数もリバートします。
引数
-
data
- abiエンコードされた呼び出しデータの配列。
戻り値
-
results
- 各呼び出しの結果をabiエンコードしたデータの配列
multicallPayable
function multicallPayable(bytes[] calldata data, uint256[] values) external payable virtual returns (bytes[] memory results)
概要
複数のabiエンコードされた呼び出しデータの配列とそれに対応するEtherの値を受け取り、それぞれのデータを使用して自身をデリゲートコールし、結果をabiエンコードして返す関数。
詳細
提供された呼び出しデータの配列に含まれる各データを順番に処理し、それぞれのデータを使用してコントラクト自体をデリゲートコールします。
各呼び出しの結果はabiエンコードされ、配列で返されます。
もしデリゲートコールの中で一つでもrevert
が発生した場合、この関数もリバートします。
また、Etherの値も指定された呼び出しに対応するように提供する必要があります。
オプション機能として提案されています。
引数
-
data
- abiエンコードされた呼び出しデータの配列。
-
values
- 各呼び出しに対応するEtherの値の配列。
- 合計が
msg.value
以下である必要があります。
戻り値
-
results
- 各呼び出しの結果をabiエンコードしたデータの配列。
補足
multicallPayable
は、msg.value
が分割されるため、常に実装できるわけではないためオプション機能として実装しています。
後方互換性
既存のほとんどのマルチコール機能と互換性があります。
テスト
以下のJavaScriptコードは、Ethersライブラリを使用して、ERC20トークンを「amt
」単位で「addressA
」と「addressB
」の両方にアトミックに転送するものです。
amtは、ERC20トークンを転送する時に、転送する数量を指定するための単位。
ERC20については以下を参考にしてください。
await token.multicall(await Promise.all([
token.interface.encodeFunctionData('transfer', [ addressA, amt ]),
token.interface.encodeFunctionData('transfer', [ addressB, amt ]),
]));
参考実装
pragma solidity ^0.8.0;
/// Derived from OpenZeppelin's implementation
abstract contract Multicall is IMulticall {
function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
(bool success, bytes memory returndata) = address(this).delegatecall(data);
require(success);
results[i] = returndata;
}
return results;
}
}
セキュリティ考慮事項
「multicallPayable
」は、コントラクトがサポートできる場合にのみ使用すべきです。
単純な実装では、攻撃者が同じEtherを使用してpayable
(支払い可能な)関数を複数回呼び出す可能性があるため、慎重に扱う必要があります。
これにより、不正な攻撃が可能になる可能性があります。
つまり、この機能を提供する際には、セキュリティに十分な注意を払い、不正な利用を防ぐ仕組みを実装することが非常に重要です。
引用
Gavin John (@Pandapip1), "ERC-6357: Single-contract Multi-delegatecall [DRAFT]," Ethereum Improvement Proposals, no. 6357, January 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6357.
最後に
今回は「EOAアドレスが単一のトランザクションで、コントラクトの複数呼び出し機能を実行できる機能を提案している規格であるERC6357」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!