[ERC6150] NFTにLinuxのファイルシステムのような階層構造を持たせる仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、NFTにLinuxのファイルシステムのような階層構造を持たせることができる**ERC6150**についてまとめていきます！

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

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

# 概要

この標準は、**EIP721**を拡張したものです。
多層のファイルシステムのような階層型NFTを提案しています。
この標準では、親NFTや子NFTを取得するためのインターフェースを提供し、NFTが葉ノードかルートノードか、それらの間の階層関係を保持します。

# 動機
このEIPは、ファイルシステムのような階層型NFTのインターフェースを標準化し、参照実装を提供します。
階層構造は、Linuxファイルシステム階層（FHS）のように、ファイルシステムに一般的に実装されています。

![linux-hierarchy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570269/a824f7cc-7abe-4010-db94-128aa6f143d6.png)
引用: https://eips.ethereum.org/EIPS/eip-6150

ウェブサイトはしばしば、**eBay**（ホーム→エレクトロニクス→ビデオゲーム→Xbox→製品）、**Twitter**（ホーム→リスト→リスト→ツイート）、**Reddit**（ホーム→r/ethereum→投稿→ホット）のようなディレクトリやカテゴリの階層構造を使用しています。

![website-hierarchy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570269/89cf6f0a-6f10-cece-a552-2ecbd7f5f237.png)
引用: https://eips.ethereum.org/EIPS/eip-6150

1つのスマートコントラクトはルートとなり、各ディレクトリ/カテゴリを個々のNFTとして管理し、NFTの階層関係も管理します。
各NFTの`tokenURI`には、別のコントラクトアドレス、ウェブサイトリンク、またはメタデータの形式が含まれる可能性があります。

この標準を使用することで、Ethereumエコシステムの利点と進化が次の通りです：

- 階層の完全なオンチェーンストレージ。
    - これは追加のDAOコントラクトによってオンチェーンでガバナンスも可能です。
- 階層関係を管理し操作するために単一のコントラクトだけが必要。
- ディレクトリ/カテゴリの所有権をNFTとして移転可能であり、オンチェーンフォーラムなどのユースケースに適しています。
- フロントエンドから階層構造への簡単で許可不要なデータアクセス。
- 伝統的なアプリケーション（電子商取引やフォーラムなど）に理想的な構造。
- 開発者にとって理解しやすいインターフェース。
    - コンセプト的にはLinuxファイルシステムのコマンドに類似しています。

利用例は次の通りです。

### Redditのようなオンチェーンフォーラム

オンチェーンフォーラムは、Redditのようなコミュニティプラットフォームを指します。
スマートコントラクトをルートとして使用し、各カテゴリやトピックを個別のNFTとして表現します。
ユーザーはNFTを所有し、カテゴリやスレッドに参加することでコミュニケーションを行います。
投稿やコメントはトランザクションとしてチェーン上に格納され、コミュニティ全体が透明で信頼性のあるディスカッションを行います。

**Twitterのようなオンチェーンソーシャルメディア
オンチェーンソーシャルメディアは、Twitterのようなプラットフォームを指します。
ユーザーは個別のNFTとしてアカウントを持ち、ツイートや投稿はトランザクションとして記録されます。
フォロー関係やリツイートもスマートコントラクト上で管理され、ユーザー間の相互作用が透明で追跡可能になります。

### 組織構造を管理するためのオンチェーン企業
オンチェーン企業は、組織構造をブロックチェーン上で管理するための利用例です。
各部門や役職が個別のNFTとして表現され、組織の階層関係や責任範囲が明確になります。プロジェクトやタスクの進捗状況もトランザクションとして追跡され、透明性と効率性が向上します。

### eBayや個々のストアのようなオンチェーンの電子商取引プラットフォーム

オンチェーンの電子商取引プラットフォームは、eBayのようなオンラインマーケットを指します。
各商品やストアが個別のNFTとして表現され、商品情報や取引履歴がブロックチェーン上で管理されます。
購入や販売のトランザクションもチェーン上で行われ、信頼性のある取引環境が提供されます。

### 木構造を持つ任意のアプリケーション

**教育プラットフォーム**
オンチェーンの教育プラットフォームでは、コースや学習リソースが木構造で表現されます。
各コースが親ノードとなり、講義や教材が子ノードとして配置されます。
学習者は特定のコースや教材にアクセスするために対応するNFTを所有し、進捗状況や成績がチェーン上で追跡されます。
将来的には、Ethereumのデータ可用性ソリューションと外部の許可不要なデータ保持ネットワークの発展により、これらのプラットフォームのコンテンツ（投稿、商品リスト、ツイートなど）を完全にオンチェーンで保存することも可能になり、完全に分散型のアプリケーションが実現されるでしょう。

**プロジェクト管理ツール**
オンチェーンのプロジェクト管理ツールでは、タスクやサブタスクが木構造で組織されます。
プロジェクトがルートノードであり、それに関連するタスクが階層的に配置されます。
チームメンバーはタスクを所有し、進捗状況や優先順位がブロックチェーン上で管理されます。

**文芸創作プラットフォーム**
オンチェーンの文芸創作プラットフォームでは、物語や小説が木構造で構築されます。
章やエピソードがノードとして配置され、物語の展開や構造が明確になります。
読者は各章やエピソードにアクセスするために対応するNFTを所有し、作品の進行状況を楽しむことができます。

**データ視覚化ツール**
オンチェーンのデータ視覚化ツールでは、データセットやチャートが木構造で配置されます。
データセットがルートノードであり、さまざまなビジュアル化要素が子ノードとして配置されます。
ユーザーはデータの探索や分析に対応するNFTを所有し、情報を効果的に可視化します。

# 仕様

## IERC6150

この提案に準拠するすべてのコントラクトは、**EIP721**および**EIP165**インターフェースを実装する必要があります。

```javascript
pragma solidity ^0.8.0;

interface IERC6150 /* is IERC721, IERC165 */ {
    event Minted(
        address indexed minter,
        address indexed to,
        uint256 parentId,
        uint256 tokenId
    );

    function parentOf(uint256 tokenId) external view returns (uint256 parentId);

    function childrenOf(
        uint256 tokenId
    ) external view returns (uint256[] memory childrenIds);

    function isRoot(uint256 tokenId) external view returns (bool);

    function isLeaf(uint256 tokenId) external view returns (bool);
}
```

### Minted

- **Event**

`tokenId`トークンが`parentId`の下でMintされたときに発行されるイベント。

**パラメータ**

- `minter`
    - トークンをMintしたアドレス（実行者）。
- `to`
    - トークンを受け取るアドレス。
- `parentId`
    - 親トークンのID。
    - `0`の場合、Mintされたトークンはルートトークンを意味する。
- `tokenId`
    - MintされたトークンのID。

### parentOf

- **Function**

`tokenId`トークンの親トークンのIDを取得する関数。

**パラメータ**

- `tokenId`
    - 子トークンのID。

**戻り値**

- `parentId`
    - 親トークンのID。

### childrenOf

- **Function**

`tokenId`トークンの子トークンのID配列を取得する関数。

**パラメータ**

- `tokenId`
    - 親トークンのID。

**戻り値**

- `childrenIds`
    - 子トークンのID配列。

### isRoot

- **Function**

`tokenId`トークンがルートトークンかどうかを確認する関数。
ルートトークンであれば`true`、そうでなければ`false`を返します。

**パラメータ**

- `tokenId`
    - 確認したいトークンのID。

**戻り値**

- `true`（ルートトークン）または`false`（ルートトークンでない）。

### isLeaf

- **Function**

`tokenId`トークンがリーフトークンかどうかを確認する関数。
リーフトークンであれば`true`、そうでなければ`false`を返します。

**パラメータ**

- `tokenId`
    - 確認したいトークンのID。

**戻り値**

- `true`（リーフトークン）または`false`（リーフトークンでない）。

## IERC6150Enumerable

「**列挙可能**」にするオプションの拡張機能です。

```javascript
interface IERC6150Enumerable is IERC6150 /* IERC721Enumerable */ {
    function childrenCountOf(uint256 parentId) external view returns (uint256);

    function childOfParentByIndex(
        uint256 parentId,
        uint256 index
    ) external view returns (uint256);

    function indexInChildrenEnumeration(
        uint256 parentId,
        uint256 tokenId
    ) external view returns (uint256);
}
```

### childrenCountOf

- **Function**

`parentId`トークンの下にある子トークンの総数を取得する関数。

**パラメータ**

- `parentId`
    - 親トークンのID。

**戻り値**

- `parentId`
    - トークンの下にある子トークンの総数。

### childOfParentByIndex

- **Function**

`parentId`トークンの下にあるすべての子トークンのうち、指定されたインデックス位置のトークンIDを取得する関数。
`parentId`が`0`の場合、ルートトークンのIDを取得します。

**パラメータ**

- `parentId`
    - 親トークンのID。
- `index`
    - インデックスの位置。

**戻り値**

- 指定されたインデックス位置の子トークンのID。

### indexInChildrenEnumeration

- **Function**

指定された親トークンの下の子トークンの配列の中で、指定されたトークンのインデックス位置を取得する関数。
`tokenId`が配列内で見つからない場合、エラーが発生します。
`parentId`が`0`の場合、ルートトークンのインデックスを取得します。

**パラメータ**

- `parentId`
    - 親トークンのID。
- `tokenId`
    - 検索対象のトークンID。

**戻り値**

- 指定されたトークンの配列内でのインデックス位置。

## IERC6150Burnable

「**焼却**」できるようにするオプションの拡張機能です。

```javascript
interface IERC6150Burnable is IERC6150 {
    function safeBurn(uint256 tokenId) external;

    function safeBatchBurn(uint256[] memory tokenIds) external;
}
```

### safeBurn

- **Function**

`tokenId`トークンを燃やす（削除する）関数。

**パラメータ**

- `tokenId`
    - 燃やす（削除する）トークンのID。

**処理**

1. `tokenId`がリーフトークンでない場合、エラーが発生します。
2. `tokenId`が有効なNFTでない場合、エラーが発生します。
3. `owner`が`tokenId`トークンの所有者でない場合、エラーが発生します。
4. `msg.sender`が現在のNFTの所有者、承認されたオペレータ、またはこのトークンの承認済みアドレスでない場合、エラーが発生します。

### safeBatchBurn

- **Function**

複数のトークンを一括で燃やす（削除する）関数。

**パラメータ**

- `tokenIds`
    - 燃やす（削除する）トークンのID配列。

**処理**

1. `tokenIds`のいずれかがリーフトークンでない場合、エラーが発生します。
2. `tokenIds`のいずれかが有効なNFTでない場合、エラーが発生します。
3. `owner`がすべての`tokenIds`トークンの所有者でない場合、エラーが発生します。
4. `msg.sender`が現在の所有者、承認されたオペレータ、またはすべての`tokenIds`に対する承認済みアドレスでない場合、エラーが発生します。

## IERC6150ParentTransferable

「**親トークンを変更する**」オプションの拡張機能です。

```javascript
interface IERC6150ParentTransferable is IERC6150 {
    event ParentTransferred(
        uint256 tokenId,
        uint256 oldParentId,
        uint256 newParentId
    );

    function transferParent(uint256 newParentId, uint256 tokenId) external;

    function batchTransferParent(
        uint256 newParentId,
        uint256[] memory tokenIds
    ) external;
}
```

### ParentTransferred

- **Event**

`tokenId`トークンの親トークンが変更されたときに発行されるイベント。

**パラメータ**

- `tokenId`
    - 変更されたトークンのID。
- `oldParentId`
    - 変更前の親トークンのID。
- `newParentId`
    - 新しい親トークンのID。

### transferParent

- **Function**

`tokenId`トークンの親トークンを新しい親トークンに変更する関数。

**パラメータ**

- `newParentId`
    - 新しい親トークンのID。
- `tokenId`
    - 変更対象のトークンのID。

### batchTransferParent

- **Function**

複数のトークンの親トークンを一括で新しい親トークンに変更する関数。

**パラメータ**

- `newParentId`
    - 新しい親トークンのID。
- `tokenIds`
    - 変更対象のトークンのID配列。

## IERC6150AccessControl

「**アクセスコントロール**」機能を追加するオプションの拡張機能です。

```javascript
interface IERC6150AccessControl is IERC6150 {
    function isAdminOf(uint256 tokenId, address account)
        external
        view
        returns (bool);

    function canMintChildren(
        uint256 parentId,
        address account
    ) external view returns (bool);

    function canBurnTokenByAccount(uint256 tokenId, address account)
        external
        view
        returns (bool);
}
```

### isAdminOf

- **Function**

アカウントが指定されたトークンの管理者（`admin`）であるか確認する関数。
各トークンには複数の管理者を設定することができます。
管理者はトークンに対して特定の操作を行う権限を持ちます。
例えば、子トークンのMint、トークンの焼却、親トークンの変更などです。

**パラメータ**

- `tokenId`
    - 確認対象のトークンのID。
- `account`
    - 確認対象のアカウントアドレス。

**戻り値**

- アカウントが管理者権限を持つ場合は`true`、そうでない場合は`false`を返す。

### canMintChildren

- **Function**

指定された親トークンとアカウントが子トークンをMintできるか確認する関数。
`parentId`が`0`の場合、アカウントがルートノードをMintできるかどうかを確認します。

**パラメータ**

- `parentId`
    - 確認対象の親トークンのID。
- `account`
    - 確認対象のアカウントアドレス。

**戻り値**

- アカウントがMint権限を持つ場合は`true`、そうでない場合は`false`を返す。

### canBurnTokenByAccount

- **Function**

指定されたトークンをアカウントが燃やす（削除する）ことができるか確認する関数。

**パラメータ**

- `tokenId`
    - 確認対象のトークンのID。
- `account`
    - 確認対象のアカウントアドレス。

**戻り値**

- アカウントが指定されたトークンを燃やす権限を持つ場合は`true`、そうでない場合は`false`を返す。

# 補足
このEIP（Ethereum Improvement Proposal）の目標は、階層型NFT（Non-Fungible Token）をサポートするためのシンプルなインターフェースを提供することです。

以下はいくつかの設計上の決定とその理由です。

## NFT間の関係性
すべてのNFTは階層的な関係を持つツリーを形成します。
各NFTはツリーのノードであり、ルートノードであるか葉ノードであるか、親ノードであるか子ノードであるかがあります。

この提案は、新しいノードをMintする際に親子関係を示すための`Minted`イベントを標準化します。
ルートノードがMintされる場合、`parentId`は`0`である必要があります。
これは、トークンIDが`0`の場合、実際のノードではないことを意味します。
したがって、実際のノードのトークンIDは`0`よりも大きくなければなりません。

階層ツリーでは、上位ノードと下位ノードをクエリすることが一般的です。
そのため、この提案では`parentOf`関数を親ノードを取得するためのものとして、`childrenOf`関数をすべての子ノードを取得するためのものとして標準化します。

`isRoot`と`isLeaf`関数は、ノードがルートノードであるか葉ノードであるかをチェックすることができ、多くのケースで非常に役立ちます。

### Enumerable拡張
この提案では、子ノードを含む列挙クエリをサポートするための拡張として3つの関数を標準化します。
それぞれの関数は`parentId`を持ち、互換性のために`parentId`が`0`の場合はルートノードのクエリを意味します。

### ParentTransferable拡張
ファイルシステムなどの場合、ディレクトリやファイルを別のディレクトリに移動することがあります。
そのため、この提案では`ParentTransferable`拡張を追加して、このような状況をサポートします。

### アクセス制御
階層的な構造では、通常、1つ以上のアカウントにノードを操作する権限があります。
例えば、子ノードをMintする、ノードを転送する、ノードを燃やすなどです。
この提案では、アクセス制御権限を確認するためのいくつかの関数を標準として追加します。

# 後方互換性
この提案は**EIP721**と完全に後方互換性があります。

# 参考実装

以下に実装コードをまとめています。

https://github.com/cardene777/base-code/tree/develop/contract/contracts/ERC6150

引用: https://eips.ethereum.org/EIPS/eip-6150

# セキュリティ考慮事項
セキュリティに関する特別な考慮事項はありません。

# 引用

Keegan Lee (@keeganlee), msfew <msfew@hyperoracle.io>, Kartin <kartin@hyperoracle.io>, qizhou (@qizhou), "ERC-6150: Hierarchical NFTs," Ethereum Improvement Proposals, no. 6150, December 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6150.

# 考察

Linuxのファイルシステムのような階層構造をNFTで実現させるのは面白いと思いました。
「**このNFTはどのNFT紐づいている**」という情報を持たせることで、NFTに親子関係を持たせられるので活用方法はたくさんありそうです。
例えば、記事を書くときに、ルートのNFTをユーザーとして、リーフのNFTを記事とするなどして、どの記事が誰が書いたものかわかるなどができたら面白いですね。
上記に加えて、ルートのNFTはSBT形式にするなど。
ゲームでも活用できるかもしれないですね。
ルートのNFTをユーザーとして、リーフのNFTを各アイテムにすることで、どのユーザーがどのアイテムを持っているかがわかります。
それに加えて、アイテムの所有者が変更したときは、拡張機能を使うことでリーフのNFTの所有者である紐づいているルートのNFTを変更できます。
考えれば考えるほどわくわくしてくるので、何か作ってみたいですね。

![ERC6150.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570269/dcf5e7a0-f2ba-6804-31aa-a566f07a6333.png)


# 最後に

今回は「ガス代を他のアドレスに支払ってもらいコントラクトの処理を実行するメタトランザクションについて提案している**ERC2771**」についてまとめてきました！
いかがだったでしょうか？

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

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

# 採用強化中！

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

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

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