# [ERC7510] 異なるコントラクト間でNFTの階層構造を管理する仕組みを理解しよう！

> ERC721は、イーサリアムブロックチェーン上でユニークなアセット（NFT、非代替トークン）を表現するための標準規格です。

- URL: https://cardene.dev/articles/a1c18c38229303ae2d2e
- 公開日: 2024-03-21
- 更新日: 2024-03-21
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、派生NFTの生成、複数の親トークンのサポートなど、異なるコントラクト間でのNFT間の階層的関係管理する仕組みを提案している**ERC7510**についてまとめていきます！

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

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

他にも様々なEIPについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

## 概要

**ERC721**は、イーサリアムブロックチェーン上でユニークなアセット（NFT、非代替トークン）を表現するための標準規格です。
この標準規格に基づいて作成されたトークンは、それぞれが一意の識別子を持ち、デジタルアートやゲーム内アイテムなど、さまざまな形で利用されています。

:::message
**ERC721**については以下の記事を参考にしてください。
:::

https://chaldene.net/erc721

この標準では、**ERC721**を拡張して、異なるコントラクト間でのトークン間に階層的な関係を構築する方法を提案しています。
具体的には、NFT（非代替トークン）間に「**親子関係**」を設定できるようにするものです。

この拡張機能を持つことで、以下のような利点があります。

#### 階層構造の表現
例えば、あるNFTが「**アルバム**」を表し、その子トークンがアルバム内の「**写真**」を表すような関係を作ることができます。
これにより、複雑な所有構造や関連性を表現できるようになります。

#### クエリ機能の提供
この標準には、あるNFTの「**親トークン**」を照会するインターフェイスや、2つのNFT間に親子関係が存在するかどうかを確認する機能が含まれています。
これにより、アプリケーションはトークン間の関係を簡単に理解し、操作できるようになります。

このような拡張は、NFTの利用可能性をさらに広げ、より複雑なアプリケーションやゲーム内での利用を可能にします。
例えば、ゲーム内で「**土地**」を表すNFTがあり、その土地上に「**建物**」や「**装飾品**」を表す子トークンを配置することで、ゲームの世界を豊かに表現できるようになります。

## 動機

ここではNFT（非代替トークン）の進化として、派生NFT（新たなNFTとして生成されるNFT）の概念に焦点を当てています。
これは、既存のNFTから新しい形式や表現を持つNFTを作り出すプロセスを指します。
ここで述べられている標準は、このような派生NFT間に存在する階層的な関係を管理し記録するためのものです。

具体的には、次のような点が含まれます。

- **派生NFTの生成**
    - 例えば、2DのイメージNFTが3DモデルのNFTとして自身の派生物を発行するケースです。
    - これにより、元のNFTの新たな表現形式が可能になります。

- **複数の親NFTからの派生**
    - あるNFTが、異なるNFTからの特徴や要素を組み合わせて作られる場合があります。
    - 例として、複数のキャラクターNFTから成る映画NFTが挙げられます。
    - これは、IP（知的財産）のライセンス許諾が関係するシナリオでよく見られる状況です。

既存の**ERC6150**標準もトークン間の階層構造を導入していますが、その適用範囲は同一のスマートコントラクト内に限られています。
しかし、実際には派生NFTを新しいNFTコレクションとして別のコントラクトで作成する必要がある場合が多く、また、複数の親から派生するケースもサポートする必要があります。
これらの要件を満たすために、新たな標準が提案されているわけです。

:::message
**ERC6150**については以下の記事を参考にしてください。
:::

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

この提案された標準は、異なるコントラクト間、そして複数の親NFTから派生するトークン間の階層的関係を設定し、管理する機能を提供することで、NFTエコシステムの柔軟性と表現力をさらに高めることを目指しています。

## 仕様

### `IERC7510.sol`インターフェース

```ts
/// @notice The struct used to reference a token in an NFT contract
struct Token {
    address collection;
    uint256 id;
}

interface IERC7510 {

    /// @notice Emitted when the parent tokens for an NFT is updated
    event UpdateParentTokens(uint256 indexed tokenId);

    /// @notice Get the parent tokens of an NFT
    /// @param tokenId The NFT to get the parent tokens for
    /// @return An array of parent tokens for this NFT
    function parentTokensOf(uint256 tokenId) external view returns (Token[] memory);

    /// @notice Check if another token is a parent of an NFT
    /// @param tokenId The NFT to check its parent for
    /// @param otherToken Another token to check as a parent or not
    /// @return Whether `otherToken` is a parent of `tokenId`
    function isParentToken(uint256 tokenId, Token memory otherToken) external view returns (bool);

    /// @notice Set the parent tokens for an NFT
    /// @param tokenId The NFT to set the parent tokens for
    /// @param parentTokens The parent tokens to set
    function setParentTokens(uint256 tokenId, Token[] memory parentTokens) external;

}
```

---

#### UpdateParentTokens

```javascript
event UpdateParentTokens(uint256 indexed tokenId);
```

**概要**

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

**詳細**

このイベントは、あるNFTの親トークンのセットが変更されたときに発行されます。
これにより、アプリケーションやユーザーは、特定のNFTに関連する親トークンの更新を効率的に監視できます。
イベントはブロックチェーン上に記録されるため、後からこれらの変更を追跡することが可能です。

**パラメータ**

- `tokenId`
	- 親トークンが更新されたNFTのID。
	- このIDはインデックス付きであり、イベントフィルタリング時に使用することができます。

---

#### `parentTokensOf`

```javascript
function parentTokensOf(uint256 tokenId) external view returns (Token[] memory);
```

**概要**

あるNFTの親トークンを取得する関数。

**詳細**

この関数は、指定されたNFTの親トークンの配列を返します。
NFTが複数の親を持つことができるため、結果は`Token`構造体の配列で返されます。
`Token`構造体は、トークンが存在する**ERC721**コントラクトのアドレスと、そのコントラクト内のトークンIDを含みます。

**引数**

- `tokenId`
    - 対象となるNFTのID。

**戻り値**

- `Token[] memory`
	- 指定されたNFTの親トークンの配列。

---

#### `isParentToken`

```javascript
function isParentToken(uint256 tokenId, Token memory otherToken) external view returns (bool);
```

**概要**

他のトークンが指定されたNFTの親であるか確認する関数。

**詳細**

この関数は、`otherToken`が`tokenId`の親トークンであるかどうかを判断します。
戻り値はブール値で、`true`の場合は`otherToken`が親であることを意味し、`false`の場合は親ではないことを意味します。
これにより、特定のトークン間の親子関係の有無を簡単に確認できます。

**引数**

- `tokenId`
	- 親を確認する対象のNFTのID。
- `otherToken`
	- 親であるか確認する他のトークン。

**戻り値**

- `bool`
	- `otherToken`が`tokenId`の親であるかどうかの`bool`値。

---

#### `setParentTokens`

```javascript
function setParentTokens(uint256 tokenId, Token[] memory parentTokens) external;
```

**概要**

NFTの親トークンを設定する関数。

**詳細**

この関数を使用して、特定のNFTに対して1つ以上の親トークンを設定できます。`parentTokens`引数を通じて、親として設定したいトークンの配列を指定します。
この機能は、NFT間の階層関係を動的に管理するために使用されます。

**引数**

- `tokenId`
	- 親トークンを設定する対象のNFTのID。
- `parentTokens`
	- 設定する親トークンの配列。

---

## 補足

提案されたこの新しい標準は、既存の**ERC6150**と比較して二つの主要な違いがあります。
1つ目は、異なるコントラクト間でのトークン参照をサポートすること、そして2つ目は、複数の親トークンを許可することです。
しかし、名称に関しては全体的に一貫性を保つよう努めています。

加えて、このインターフェイスには子トークンの関係を含めていません。
理由は、派生NFTを作成する時、元となるNFTはすでに存在しているため、どの親トークンを含めるべきかを知っています。
しかし、元のNFTをミントするときには、その子トークンが何になるかはわかりません。
もし子トークンの関係を記録する必要がある場合、派生NFTをミントするたびに元のNFTに対してそれを子として追加する操作を行う必要があります。
しかし、これら二つのNFTが異なるコントラクトに属している場合、異なる書き込み権限が必要になり、実際には二つの操作を単一のトランザクションで組み合わせることは不可能です。
そのため、派生NFTからの親関係のみを記録することに決めました。

この設計決定により、NFT間の階層関係を効率的に管理しやすくなります。
親トークンの関係を明確に管理することで、NFTの起源やそれがどのように派生したかを簡単に追跡できます。
しかし、子トークンの追跡は、異なるコントラクト間の操作と権限の複雑さにより実現が難しいため、このアプローチでは採用されていません。

## 互換性

互換性の問題は見つかっていません。

## 実装

```ts
// SPDX-License-Identifier: CC0-1.0

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

import "./IERC7510.sol";

contract ERC7510 is ERC721, IERC7510 {

    mapping(uint256 => Token[]) private _parentTokens;
    mapping(uint256 => mapping(address => mapping(uint256 => bool))) private _isParentToken;

    constructor(
        string memory name, string memory symbol
    ) ERC721(name, symbol) {}

    function supportsInterface(
        bytes4 interfaceId
    ) public view virtual override returns (bool) {
        return interfaceId == type(IERC7510).interfaceId || super.supportsInterface(interfaceId);
    }

    function parentTokensOf(
        uint256 tokenId
    ) public view virtual override returns (Token[] memory) {
        require(_exists(tokenId), "ERC7510: query for nonexistent token");
        return _parentTokens[tokenId];
    }

    function isParentToken(
        uint256 tokenId, Token memory otherToken
    ) public view virtual override returns (bool) {
        require(_exists(tokenId), "ERC7510: query for nonexistent token");
        return _isParentToken[tokenId][otherToken.collection][otherToken.id];
    }

    function setParentTokens(
        uint256 tokenId, Token[] memory parentTokens
    ) public virtual override {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC7510: caller is not owner or approved");
        _clear(tokenId);
        for (uint256 i = 0; i < parentTokens.length; i++) {
            _parentTokens[tokenId].push(parentTokens[i]);
            _isParentToken[tokenId][parentTokens[i].collection][parentTokens[i].id] = true;
        }
        emit UpdateParentTokens(tokenId);
    }

    function _burn(
        uint256 tokenId
    ) internal virtual override {
        super._burn(tokenId);
        _clear(tokenId);
    }

    function _clear(
        uint256 tokenId
    ) private {
        Token[] storage parentTokens = _parentTokens[tokenId];
        for (uint256 i = 0; i < parentTokens.length; i++) {
            delete _isParentToken[tokenId][parentTokens[i].collection][parentTokens[i].id];
        }
        delete _parentTokens[tokenId];
    }

}
```

## セキュリティ

NFTの親トークンが無効なデータを指している可能性があるのは、主に2つの理由によります。

#### 親トークンが後で焼却される可能性がある
NFTの親として指定されたトークンは、後に「`burn`」されることがあります。
`burn`とは、トークンを意図的に削除し、それを使えなくするプロセスのことです。
このプロセスを経ると、そのトークンは有効ではなくなり、それを親として指定することは意味を成さなくなります。

#### `parentTokens`引数をチェックしない可能性
親トークンを設定するための関数（`setParentTokens`）を持つコントラクトは、提供された親トークンが実際に有効であるかどうかを確認しないかもしれません。
これにより、無効または存在しないトークンを親として設定する可能性が生じます。

これらの理由から、セキュリティ上の考慮から、NFTの親トークンを取得するアプリケーションは、それらが有効なトークンとして存在することを確認する必要があります。
つまり、アプリケーションは、単に親トークンの情報を受け取るだけでなく、それらのトークンが実際に存在し、有効であることを検証する追加の手順を踏むべきです。
これにより、アプリケーションが無効なデータを扱うリスクを減らし、より安全なNFTエコシステムを維持することができます。

## 引用

Ming Jiang (@minkyn), Zheng Han (@hanbsd), Fan Yang (@fayang), "ERC-7510: Cross-Contract Hierarchical NFT [DRAFT]," Ethereum Improvement Proposals, no. 7510, August 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7510.

## 最後に

今回は「派生NFTの生成、複数の親トークンのサポートなど、異なるコントラクト間でのNFT間の階層的関係管理する仕組みを提案している**ERC7510**」についてまとめてきました！
いかがだったでしょうか？

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

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

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4
