[ERC2135] NFTの使用を管理するインターフェースの仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、イベントチケットなどのNFTの消費を管理できるインターフェースを提供する**ERC2135**についてまとめていきます！

:::note info
以下にまとめられているものをChatGPTも使用しながら、翻訳・要約・補足してまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-2135

# 概要
このEIPは、Ethereum上でデジタルアセットに「**消費可能**」というマークをつけ、「**消費**」に対応するためのインターフェースを提供することを目的としています。
これにより、様々なデジタルアセットに対して、消費行為やその他のアクションに対応する標準化された方法を定義することが可能になります。

# 動機
デジタルアセットが一部の状態変化によって消費される場合があります。
例えば、コンサートチケットです。
チケット保持者がコンサート会場に入場すると、そのチケットは消費されたと見なされます。
このような消費行為に対して、一貫性のある方法を提供することが必要とされています。

:::note info
一貫性がないと以下のような懸念が生じます。

**不正な消費**
不正な行為や二重支払いのリスクが高まります。
例えば、同じアセットを複数回消費できるといった不正な操作が行われる可能性があります。

**予測性の欠如**
消費の方法や条件が一貫していない場合、ユーザーやコントラクトがデジタルアセットの挙動を予測することが困難になります。
これは予期せぬ結果やトラブルの原因となります。

**エコシステムの混乱**
デジタルアセットの消費に関する様々な方法が存在する場合、エコシステム内のアプリケーションやコントラクト間の相互運用性が低下し、混乱が生じる可能性があります。
これは開発者やユーザーの体験を損なうことになります。

**セキュリティ上のリスク**
一貫性のない消費方法は、デジタルアセットのセキュリティ上の脆弱性をもたらす可能性があります。
セキュリティが不十分な消費方法を利用することで、アセットが不正に利用されたり、攻撃者による悪意のある行為の対象になる可能性があります。

**法的・規制上の問題**
特定のデジタルアセットには法的な制約や規制が存在する場合があります。
一貫性がない消とこれらの法的・規制上の問題に対処することを困難にする可能性があります。
:::

標準的なインターフェースを持つことにより、サービス、クライアント、UI、コントラクト間の機能が相互運用可能となります。
つまり、異なるアプリケーションやコントラクトがこのインターフェースに準拠することで、デジタルアセットの消費に関連する処理や情報のやり取りが容易になります。
これにより、シームレスなエコシステムが形成され、デジタルアセットをより便利に活用することができるでしょう。

このようなEIPは、ブロックチェーン技術の進化と共に、さまざまなデジタルアセットの管理や取引において重要な役割を果たすことが期待されています。それに伴い、開発者やプロジェクトが共通のルールとインターフェースに基づいて作業できるため、効率的なエコシステムの構築に寄与すると言えます。

# 仕様

このEIPに準拠するコントラクトは、以下のインターフェイスを実装する。

```javascript
pragma solidity >=0.7.0 <0.9.0;

interface IERC2135 {
    function consume(
        address _consumer,
        uint256 _assetId,
        uint256 _amount,
        bytes calldata _data
    ) external returns (bool _success);

    function isConsumableBy(
        address _consumer,
        uint256 _assetId,
        uint256 _amount
    ) external view returns (bool _consumable);
               or future extension.
    event OnConsumption(
        address indexed consumer,
        uint256 indexed assetId,
        uint256 amount,
        bytes data
    );
}
```

## consume

トークンの消費を実行をする関数。
消費が成功した場合は、`true`を返します。

### parameter

- `_consumer`
    - トークンを消費するアドレス。
- `_assetId`
    - 消費されるNFTアセットのトークンID。
- `_amount`
    - 消費されるアセットの量。
- `_data`
    - 任意の追加データ。

### isConsumableBy

指定されたトークンが指定された量で消費可能かどうかをチェックする関数。
消費可能な場合は`true`を返します。

### parameter

- `_consumer`
    - トークンを消費するアドレス。
- `_assetId`
    - 消費されるNFTアセットのトークンID。
- `_amount`
    - 消費されるアセットの量。

## OnConsumption

トークンの消費が成功した場合に発行されるイベント。
消費されたトークンの情報や任意の追加データが含まれます。

### parameter

- `_consumer`
    - トークンを消費するアドレス。
- `_assetId`
    - 消費されるNFTアセットのトークンID。
- `_amount`
    - 消費されるアセットの量。
- `_data`
    - 任意の追加データ。

## 仕様の補足

**ERC721**や**ERC1155**というイーサリアムのトークン規格は、非代替可能トークン（NFT）や複数のトークンをまとめて管理するための標準化された方法を提供します。
これらの規格に準拠したコントラクトが存在する場合、消費が行われた際にはいくつかのイベントが発行されます。

:::note info
**ERC721**については以下を参考にしてください。
:::

https://chaldene.net/erc721

:::note info
**ERC1155**については以下を参考にしてください。
:::

https://chaldene.net/erc1155

1. `Transfer`イベント / `TransferSingle`イベント:

**ERC721**や**ERC1155**では、トークンの所有権が変更された場合、`Transfer`イベントまたは`TransferSingle`イベントが発行されます。
`Transfer`イベントは、**ERC721**の単一のトークンが所有者間で移動する場合に使用されます。
一方、`TransferSingle`イベントは、**ERC1155**の複数のトークンの所有者が一括して変更される場合に使用されます。

2. `OnConsumption`イベント:
**ERC2135**で定義された`OnConsumption`イベントは、トークンの消費が成功した場合に発行されます。
つまり、トークンが消費されたことを示すイベントです。

**ERC721**や**ERC1155**に準拠したコントラクトの`consume`メソッドが成功すると、次のようなフローになります。
1. トークンが消費されたことを示す`OnConsumption`イベントが発行されます。
2. トークンの所有権が変更され、`Transfer`イベントまたは`TransferSingle`イベントが発行されます。
    - このとき、トークンは現在の所有者から`0`アドレスに移動したかのように振る舞います。

これにより、他のコントラクトやアプリケーションがこれらのイベントを監視してトークンの状態変化を追跡できるようになります。
例えば、デジタルアセットを所有しているユーザーがそれを消費した場合、他のアプリケーションはそれに対応して何らかのアクションを起こすことができます。
また、トークンの移動と消費に関する情報が正確に記録されることで、エコシステム全体の一貫性と相互運用性が確保されます。

# 補足

このEIPでは、デジタルアセットの「**消費**」に関連するアクションを実行するためのインターフェースが提案されています。
しかし、消費が行われる具体的な条件や消費を実行する権限については仮定していません。
これは、様々なアセットや使用ケースに対応できる柔軟性を持たせるためです。

具体的な理由として以下が挙げられます。

1. **汎用性の確保**
消費に関連する条件や権限は、プロジェクトや使用ケースによって異なる場合があります。
このEIPは、特定の制約を設けずに汎用的なインターフェースを提供することで、様々なアセットに対応できるようになっています。

2. **ERC721やERC1155との互換性**
アセットの識別には`uint256`のアセットIDが使用されるため、**ERC721**のアセットIDとの互換性を持たせることが考慮されています。
これにより、既存の**ERC721**や**ERC1155**コントラクトとも容易に統合できるようになります。

3. **拡張性の考慮**
このEIPでは、関数やイベントの追加による拡張性を重視しています。
例えば、`_data`フィールドの追加は、将来的な機能拡張や追加情報の付与を容易にします。

4. **シンプルなデザイン**
このEIPは、消費可能なアセットの作成に関連する機能やイベントを含めないシンプルなデザインになっています。
これは、作成に関する具体的な使用ケースに応じた機能を他の標準で追加することを想定しているためです。

:::note info
例えば、あるプロジェクトでは、特定のアセットを作成する際には特定の条件が必要とされるかもしれません。
また別のプロジェクトでは、作成されるアセットに特定の属性情報やメタデータが必要になる場合があります。
このように、アセットの作成にはそれぞれ異なる要件や情報が関連付けられることがあります。
このEIPは消費に焦点を当て、作成に関する具体的な機能は別の標準として追加することを想定しています。
:::

また、このインターフェースにはアセットに関連するメタデータも含まれていません。
メタデータは、別個の拡張インターフェースを使用して必要に応じて追加できるようになっています。

:::note info
このEIPでは、消費アクションに関するインターフェースを提供していますが、消費対象のアセットに関連する「**メタデータ**」は、このインターフェースに含まれていません。
しかし、必要な場合は「**メタデータ**」を扱うための別個の拡張インターフェースを使用することができるようになっています。

では、「**メタデータ**」とは何でしょうか？
メタデータとは、アセットに関する追加の情報や属性情報のことを指します。
例えば、あるNFT（非代替可能トークン）があったとして、そのNFTが何を表しているかを説明するテキストや画像、作成者の情報などが「**メタデータ**」に該当します。

このEIPでは、「**メタデータ**」を直接的に扱う機能は提供されていませんが、必要に応じて拡張インターフェースを使用して「**メタデータ**」を追加できるようになっています。
つまり、アセットの作成者や利用者が「**メタデータ**」を追加したい場合、別個の拡張インターフェースを作成して、そこに「**メタデータ**」に関する機能を定義することができるのです。

これにより、アセットに関連する情報を柔軟に拡張できるようになります。
例えば、あるアプリケーションが特定のアセットに対して「**メタデータ**」を利用してさらなる情報を提供したい場合、そのアプリケーションは別途拡張インターフェースを実装し、必要な「**メタデータ**」に対する処理をそこで行うことができるのです。

このように、「**メタデータ**」は消費アクションに直接的には関係しないが、必要な場合は別個の拡張インターフェースを利用して追加することができる仕組みが提供されています。
これにより、アセットに関する情報をさまざまな方法で管理・表示できるようになります。
:::

さらに、`consume`関数と`isConsumableBy`関数に`address`型の`consumer`を含めることで、アセットの消費者を指定する柔軟性を持たせています。
これにより、トランザクションのイニシエータ以外の別のユーザーがアセットを消費できるようになります。

:::note info
このEIPでは、アセットの「**消費**」に関するインターフェースを定義していますが、その中で「**消費**」に特定のユーザーを制限することはしていません。
つまり、トランザクションのイニシエータ（トランザクションを発行したユーザー）以外の別のユーザーも、アセットを消費することができるようになります。

これは、「`consume`」関数および「`isConsumableBy`」関数に`address`型の「`consumer`」パラメータが含まれていることにより実現されています。

例えば、あるアセットがNFTとして**ERC721**に準拠しているとします。
通常、**ERC721**の場合、特定のNFTを所有しているアドレス（トランザクションのイニシエータ）以外のユーザーは、そのNFTを送付したり変更したりすることはできません。

しかし、このEIPにより、「`consume`」関数の「`consumer`」パラメータに他のユーザーのアドレスを指定することで、そのユーザーもアセットを消費することができます。
つまり、トランザクションのイニシエータ以外の別のユーザーがアセットを消費できるようになるのです。

これにより、アセットの消費を特定のユーザーに制限せず、広く利用可能にすることができます。
たとえば、あるアプリケーションが特定のNFTを保有しているユーザー以外にもアセットの消費を許可したい場合、このEIPに従って「`consumer`」パラメータを指定することで、そのような機能を実現できるのです。

この柔軟性により、アセットの消費が特定のユーザーに制約されず、より多様な使用ケースやアプリケーションに対応できるようになります。
:::

最後に、**ERC721**や**ERC1155**だけでなく、他のトークン標準（**ERC20**、**ERC777**）や**ERC1155**のFungible Tokenとも互換性があるように設計されています。
これにより、さまざまなトークンの種類に対応する柔軟性があります。


# テストケース

```javascript
describe("Consumption", function () {
    it("Should consume when minted", async function () {
      const fakeTokenId = "0x1234";
      const { contract, addr1 } = await loadFixture(deployFixture);
      await contract.safeMint(addr1.address, fakeTokenId);
      expect(await contract.balanceOf(addr1.address)).to.equal(1);
      expect(await contract.ownerOf(fakeTokenId)).to.equal(addr1.address);
      expect(await contract.isConsumableBy(addr1.address, fakeTokenId, 1)).to.be.true;
      const tx = await contract.consume(addr1.address, fakeTokenId, 1, []);
      const receipt = await tx.wait();
      const events = receipt.events.filter((x: any) => { return x.event == "OnConsumption" });
      expect(events.length).to.equal(1);
      expect(events[0].args.consumer).to.equal(addr1.address);
      expect(events[0].args.assetId).to.equal(fakeTokenId);
      expect(events[0].args.amount).to.equal(1);
      expect(await contract.balanceOf(addr1.address)).to.equal(0);
      await expect(contract.ownerOf(fakeTokenId))
        .to.be.rejectedWith('ERC721: invalid token ID');
      await expect(contract.isConsumableBy(addr1.address, fakeTokenId, 1))
        .to.be.rejectedWith('ERC721: invalid token ID');
    });
  });

  describe("EIP-165 Identifier", function () {
    it("Should match", async function () {
      const { contract } = await loadFixture(deployFixture);
      expect(await contract.get165()).to.equal("0xdd691946");
      expect(await contract.supportsInterface("0xdd691946")).to.be.true;
    });
  });
```

# 参考実装

アドレス`0x3682bcD67b8A5c0257Ab163a226fBe07BF46379B`コントラクトがGoerliテストネットにデプロイされています。

https://goerli.etherscan.io/address/0x3682bcD67b8A5c0257Ab163a226fBe07BF46379B#code

# セキュリティに関する考慮事項

## トークンの消費による残高変更への注意
このEIPに準拠したコントラクトは、トークンが消費された際の残高変更に注意する必要があります。
消費アクションが行われると、アセット（トークン）の所有者が変わる場合があります。
そのため、消費アクションが正しく行われた場合には、トークンの残高を適切に変更する必要があります。
特に非代替可能トークン（NFT）の場合、一つのトークンを複数のアカウントが同時に所有することができないため、消費アクションによる残高変更は慎重に行われるべきです。

## コントラクトの一時停止と消費可能性の一貫性
コントラクトが一時停止される場合や、特定のユーザーがトークンの送付を制限される場合、消費可能性に関連する制約も適切に処理されるべきです。
例えば、一時停止中はトークンの消費ができないようにするなど、トークンの消費とコントラクトの状態変更が一貫していることが重要です。

## consumeメソッドのアクセス制御
このEIPに準拠したコントラクトは、自分自身の使用ケースにおいて、どのEOA（外部所有者アカウント）またはコントラクトアカウントが`consume`メソッドを開始できるかを慎重に定義する必要があります。
誰がトークンを消費できるかは、プロジェクトやアプリケーションの要件によって異なる場合があります。
適切なアクセス制御を行わないと、不正なアクションやセキュリティ上のリスクが発生する可能性があります。

## セキュリティ監査とテスト
消費アクションに関連するセキュリティは非常に重要です。
コントラクトのコードやアクセス制御を十分に監査し、セキュリティ上の問題がないかを確認するためにセキュリティ監査を実施することが推奨されます。
また、適切なテストを行って、消費関連の機能が予想どおりに機能していることを確認することも重要です。

これらのセキュリティに関する考慮事項を適切に対応して、準拠したコントラクトの安全性と信頼性を確保することが重要です。
セキュリティへの配慮が不十分な場合、悪意ある攻撃やトークンの不正な消費などの問題が発生する可能性があるため、慎重な対応が必要です。

# 引用

Zainan Victor Zhou (@xinbenlv), "ERC-2135: Consumable Interface (Tickets, etc)," Ethereum Improvement Proposals, no. 2135, June 2019. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2135.

# 最後に

今回は「イベントチケットなどのNFTの消費を管理できるインターフェースを提供する**ERC2135**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

# 採用強化中！

CryptoGamesでは一緒に働く仲間を大募集中です。

https://www.wantedly.com/companies/cryptogames

この記事で書いた自分の経験からもわかるように、裁量権を持って働くことができて一気に成長できる環境です。
「**ブロックチェーンやWeb3、NFTに興味がある**」、「**スマートコントラクトの開発に携わりたい**」など、少しでも興味を持っている方はまずはお話ししましょう！
ぜひTwitterのDMか上記Wantedlyから連絡ください！