Appearance
L1-14: ERC-1155 Game Items(マルチトークン標準)
1. 課題
ERC-1155 マルチトークン標準コントラクトを実装します。単一のコントラクトで複数のトークンタイプ(代替可能トークン FT + 非代替可能トークン NFT)を同時に管理し、バッチ転送、バッチ残高照会、および Owner による新規トークンタイプの作成とミントをサポートします。
典型的なゲームシナリオ:1つのコントラクトに金貨(FT、代替可能)、レアスキン(NFT、1点限定)、ポーション(セミ FT、100本限定)が同時に存在します。
2. なぜ重要か
ERC-721 ではトークンの種類ごとに個別のコントラクトが必要です。数百種類のアイテムが存在するゲームでは、ガスコストが許容できません。ERC-1155 はこの問題を解決します。
- 単一コントラクトで複数トークン:1つのコントラクトで理論上無制限のトークンタイプを管理
- バッチ操作:
safeBatchTransferFromで1回のトランザクションで複数トークンを転送 -- 約50%のガス削減 - バッチ照会:
balanceOfBatchで複数アドレス/トークンの残高を一度に照会 -- フロントエンドのパフォーマンスが大幅に向上 - FT + NFT の混在:同じコントラクトで代替可能トークンと非代替可能トークンを両方含むことが可能
ERC-1155 は ERC-721 の代替ではなく補完的な標準です。ゲーム(Axie Infinity、Gods Unchained)、メンバーシップカード、および複数のトークンタイプが必要なあらゆるシナリオで広く使用されています。
3. 解決策
コントラクトアーキテクチャ
完全な IERC-1155 インターフェースをゼロから実装します(理解を深めるため、OpenZeppelin の実装は継承しません):
solidity
contract GameItems is IERC1155, Ownable {
// tokenId => owner => balance
mapping(uint256 => mapping(address => uint256)) private _balances;
// owner => operator => approved
mapping(address => mapping(address => bool)) private _operatorApprovals;
// tokenId => URI
mapping(uint256 => string) private _tokenURIs;
uint256 private _tokenIdCounter;
}
コアインターフェース
| メソッド | 説明 |
|---|---|
balanceOf(owner, id) | 単一トークンの残高照会 |
balanceOfBatch(owners[], ids[]) | バッチ残高照会 |
safeTransferFrom(from, to, id, amount, data) | 単一トークン転送 |
safeBatchTransferFrom(from, to, ids[], amounts[], data) | バッチ転送 |
setApprovalForAll(operator, approved) | オペレーターに全トークンの管理を承認 |
トークン作成
Owner のみが新しいトークンタイプを作成できます:
solidity
function createToken(
string calldata _uri,
address[] calldata _recipients,
uint256[] calldata _amounts
) external onlyOwner returns (uint256) {
uint256 tokenId = _tokenIdCounter++;
_tokenURIs[tokenId] = _uri;
for (uint256 i = 0; i < _recipients.length; i++) {
_balances[tokenId][_recipients[i]] += _amounts[i];
emit TransferSingle(msg.sender, address(0), _recipients[i], tokenId, _amounts[i]);
}
return tokenId;
}
4. 遭遇した落とし穴
4.1 safeTransferFrom のコールバック安全チェック
ERC-1155 では、コントラクトアドレスへの転送時に、受信側が IERC1155Receiver インターフェースを実装している必要があります。このチェックを忘れると、ERC-1155 を処理できないコントラクトにトークンが永久にロックされます。
実装は以下のようにする必要があります:
solidity
function _doSafeTransferAcceptanceCheck(...) private {
if (to.code.length > 0) {
try IERC1155Receiver(to).onERC1155Received(...) returns (bytes4 response) {
require(response == IERC1155Receiver.onERC1155Received.selector, "ERC1155 rejected");
} catch {
revert("ERC1155 transfer to non-ERC1155Receiver");
}
}
}
4.2 バッチ操作における配列長の検証
safeBatchTransferFrom と balanceOfBatch はどちらも複数の配列パラメータを受け取ります。すべての配列長が等しいことを確認する必要があります。そうしないと、配列の範囲外アクセスやロジックエラーが発生します。
4.3 Approval モデルの違い
ERC-1155 は setApprovalForAll(オールオアナッシング)を使用し、ERC-721 の approve(個別承認)はありません。つまり、あるオペレーターを一度承認すると、そのオペレーターはあなたのすべてのトークンタイプを操作できるようになります。
4.4 supportsInterface の実装
ERC-165 インターフェース検出はすべての ERC 標準の要件です。supportsInterface が誤った値を返すと、マーケットプレイスやウォレットがあなたのコントラクトを ERC-1155 として認識できない可能性があります。
5. 落とし穴の原因
5.1
ERC-1155 の安全な転送メカニズムは、トークン喪失を防ぐ重要な防衛線です。このチェックがないと、ユーザーが誤ってゲームアイテムを取引所コントラクト(ERC-1155 の処理方法を知らない)に転送し、アイテムが永久に失われる可能性があります。
5.2
Solidity は配列長を自動的にチェックしません。呼び出し元が ids.length=3 で amounts.length=2 を渡す可能性があります。関数の冒頭で検証しないと、ループが存在しないインデックスにアクセスして revert し(ガスの無駄)、さらに悪い結果を招く可能性があります。
6. 落とし穴の解決方法
- 複数配列を受け取るすべての関数の冒頭で検証する:
require(ids.length == amounts.length, "length mismatch") - 完全な
_doSafeTransferAcceptanceCheckと_doSafeBatchTransferAcceptanceCheckを実装する supportsInterfaceが正しいインターフェース ID を返す:type(IERC1155).interfaceIdtransferFromではなくsafeTransferFromを使用する(ERC-1155 標準には safe バージョンのみ存在)createTokenでTransferSingleイベントを発行する(from = address(0) はミントを表す)
7. 技術的ポイント
| ポイント | 説明 |
|---|---|
| 単一コントラクト複数トークン | tokenId でタイプを区別し、複数コントラクトのデプロイを代替 |
| バッチ転送 | safeBatchTransferFrom で複数トークンを一度に転送 |
| バッチ照会 | balanceOfBatch で複数の残高を一度に照会 |
| FT + NFT 混在 | tokenId の総供給量が FT (>1) か NFT (=1) かを決定 |
| IERC1155Receiver | コントラクト受信側が実装すべき安全コールバックインターフェース |
| URI システム | トークンタイプごとに独立したメタデータ URI |
| Approval モデル | setApprovalForAll オールオアナッシング承認 |