[ERC5646] トークンの状態を追跡できるインターフェースの仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、トークンの実装や仕組みを知らなくても、トークンの状態を追跡できるインターフェースを提案している**ERC5646**についてまとめていきます！

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

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

# 概要

この規格では、ミュータブル（可変）トークンと呼ばれる状態が変更できるトークンに関連しています。
例えば、ゲーム内のアイテムやデジタルアセットがこれに当たります。

この規格では、可変トークンの状態を認識するために、実際の内部実装の詳細を知らなくても、特定の方法でトークンの状態を判別できる手段を提供しています。
トークンがどのような状態にあるのかを知るために、トークン自体の内部の仕組みについて深く理解する必要はありません。

この規格の目的は、開発者やユーザーがトークンの状態を追跡しやすくすることです。
これにより、例えばトークンがどのユーザーの所有物であるかや、特定の条件を満たしているかどうかなどを確認しやすくなります。
また、この規格を活用することで、トークンを操作する際に便利な機能やアプリケーションを開発する基盤が整備されることが期待されています。

# 動機

プロトコルは、トークンがどのような状態にあるのかに関する情報を知る必要があります。
そして、その情報を元にして、各トークンを個別に特定できる方法を作成しています。
つまり、プロトコルはトークンが持つ性質や特徴に基づいて、それぞれのトークンを区別するための手段を考えています。
ただ、この方法では新しいトークンが増えるたびに、それぞれのトークンに合わせた対応が必要です。

この問題の背景をもう少し詳しく説明しますと、新しいトークンが導入されるたびに、そのトークンの特定の性質や状態に関する情報をプロトコルが事前に知っておかなければなりません。
その結果、新しいトークンごとにプロトコル自体を変更したり、機能を追加したりする必要が出てきます。

この状況がもたらす難点は、プロトコルの開発や保守が複雑化することです。
新しいトークンが増えるたびに、追加の対応やコードの変更が必要になると、プロトコル全体のスケーラビリティや効率性に問題が生じることがあります。

この課題に対処するために今回の規格が提案されています。
この規格は、新しいトークンの状態についての詳細な情報をプロトコルが知らなくても、トークンを一意に特定できる方法を提供します。
これによって、プロトコルが新しいトークンをサポートするために大幅な変更を必要とせず、スムーズな運用が可能になります。

![support-per-abi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570269/42d228aa-52df-f616-e280-9d9fc0e5f56b.png)

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

# 仕様

```javascript
pragma solidity ^0.8.0;

interface ERC5646 is ERC165 {

    /// @notice Function to return current token state fingerprint.
    /// @param tokenId Id of a token state in question.
    /// @return Current token state fingerprint.
    function getStateFingerprint(uint256 tokenId) external view returns (bytes32);

}
```

1. `getStateFingerprint`関数は、トークンの状態が変わるときには異なる値を返す必要があります。
トークンの状態が変化するたびに、新しい識別値を生成する仕組みです。

2. `getStateFingerprint`関数は、トークンの状態が変わらない場合には同じ値を返すべきです。

3. `getStateFingerprint`関数は、トークンのライフサイクル中に変わる可能性があるすべての状態プロパティを含むべきです。
つまり、変更可能なプロパティに関する情報が含まれることで、トークンの状態を正確に識別できるようにします。

4. `getStateFingerprint`関数は、計算された値を含むことができます。
たとえば、現在のタイムスタンプに基づく値（有効期限、成熟度など）を含むことができます。

5. `getStateFingerprint`関数は、トークンのメタデータのURI（Uniform Resource Identifier）を含むことができます。
これは、トークンに関連する情報へのリンクなどが含まれることを意味します。

6. `supportsInterface(0xf5112315)`関数は、特定のインターフェースをサポートするかどうかを判定する関数です。
このインターフェースが指定された場合、関数は真（`true`）を返します。

# 補足

1. プロトコルは、トークンの識別子の一部として状態の指紋（`state fingerprint`）を使用でき、状態の具体的な実装の詳細を知らずに可変トークンをサポートできます。

2. 状態の指紋は、不変な状態プロパティを考慮する必要はありません。
なぜなら、これらのプロパティはトークンのIDによって安全に識別できるからです。

3. この規格は、トークンの状態に関するプロパティの詳細な知識が必要な使用ケースには適用されません。
なぜなら、このようなケースでは、前に述べたようなボトルネックの問題を解決することが難しいからです。

:::note info
特定のトークンの状態のプロパティについて深く知識が必要な場合、そのトークンの識別や管理には個別のアプローチが必要です。
状態のプロパティが複雑で、トークンごとに異なる場合、プロトコルが新しいトークンをサポートする際に大きな手間や複雑さが生じる可能性があります。
そのため、この規格は、こうした場合には適用されないことを意味しています。

状態のプロパティ知識が必要なケースでは、他の方法やアプローチを検討する必要があります。
この規格は、プロトコルが状態のプロパティに依存せずに効果的に可変トークンを管理する手段を提供するものですが、一部の特定のケースでは限定的な可能性しか提供できないということを意味しています。
:::

![support-per-eip.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570269/e1669db9-269b-4a67-4eab-3566eca513cd.png)

:::note info
**フィンガープリント**
特定のトークンの状態を一意に識別するための手段です。
トークンの状態は、アセットの所有者や数量、手数料、有効期限などの要素で定義されています。
しかし、これらの要素が変わるたびにプロトコルを変更せずに、トークンを正確に管理したいと考えています。

そのために、フィンガープリントはトークンの状態を要約したもので、状態の重要な部分をハッシュ化して生成します。
このハッシュは状態の要素に基づいて計算されるため、状態が変わるたびに異なる値になります。
フィンガープリントは、特定のトークンの状態を効率的に特定し、異なる状態を区別するための指針として使用されます。

提供されたコントラクト内では、`getStateFingerprint`関数がフィンガープリントを計算しています。
この関数は、特定のトークンIDに対応する状態情報を取得し、その情報を元にアセット、数量、有効期限などの要素をエンコードしてハッシュ化します。
このハッシュ値がトークンのフィンガープリントであり、トークンの状態変化を素早く検出し、正確に管理するための手がかりとなります。
:::

# 後方互換性

特に問題なし。

# 参考実装

```javascript
pragma solidity ^0.8.0;

/// @title Example of a mutable token implementing state fingerprint.
contract LPToken is ERC721, ERC5646 {

    /// @dev Stored token states (token id => state).
    mapping (uint256 => State) internal states;

    struct State {
        address asset1;
        address asset2;
        uint256 amount1;
        uint256 amount2;
        uint256 fee; // Immutable
        address operator; // Immutable
        uint256 expiration; // Parameter dependent on a block.timestamp
    }


    /// @dev State fingerprint getter.
    /// @param tokenId Id of a token state in question.
    /// @return Current token state fingerprint.
    function getStateFingerprint(uint256 tokenId) override public view returns (bytes32) {
        State storage state = states[tokenId];

        return keccak256(
            abi.encode(
                state.asset1,
                state.asset2,
                state.amount1,
                state.amount2,
                // state.fee don't need to be part of the fingerprint computation as it is immutable
                // state.operator don't need to be part of the fingerprint computation as it is immutable
                block.timestamp >= state.expiration
            )
        );
    }

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

}
```


### states

**概要**
トークンのIDに対応する状態を保持するためのマッピング配列。

**詳細**
トークンのIDとそれに関連する状態情報（`State`構造体）を関連付けて保持します。

---

### State

**概要**
トークンの状態情報を表す構造体。

**詳細**
以下のプロパティを持ちます。
- `asset1`
    - アセット1のアドレス。
- `asset2`
    - アセット2のアドレス。
- `amount1`
    - アセット1の数量。
- `amount2`
    - アセット2の数量。
- `fee`
    - 手数料（不変）。
- `operator`
    - オペレーターのアドレス（不変）。
- `expiration`
    - 有効期限（ブロックのタイムスタンプに依存）。

---

### getStateFingerprint

**概要**
指定されたトークンの状態に対応する指紋（ハッシュ値）を取得する関数。

**詳細**
指定されたトークンIDに対応する状態を取得し、一部のプロパティを元にを計算して返します。
手数料とオペレーターのプロパティは不変なので指紋に含まれません。
また、有効期限はブロックのタイムスタンプに依存するため、その条件も指紋に含まれます。

**引数**
- `tokenId`
    - 対象のトークンのID。

**戻り値**
- `bytes32`
    - トークンの状態指紋（ハッシュ値）。

---

#### supportsInterface

**概要**
指定されたインターフェースがサポートされているかどうかを判定する関数。

**詳細**
親コントラクトで定義されたインターフェースをサポートしているかどうかを判定し、結果を返します。
また、**ERC5646**のインターフェースにも対応しています。

**引数**
- `interfaceId`
    - 判定するインターフェースの識別子。

**戻り値**
- `bool`
    - インターフェースをサポートしているかの`bool`値。

---

# セキュリティ考慮事項


### トークン状態のフィンガープリントが異なるコントラクト間で衝突する可能性がある

異なるコントラクトで同じ状態を持つトークンが生成された場合、その状態のフィンガープリントが同じになる可能性があります。
このため、トークンの正確な状態を確認する際には、異なるコントラクト間でのフィンガープリントの衝突を防ぐために、同じトークンコントラクト内でのみフィンガープリントを比較すべきです。

### getStateFingerprintの実装にすべての状態変更可能なパラメータを含めない場合のリスク

トークンの状態を変更するために使用されるパラメータがフィンガープリントに含まれない場合、トークンの所有者はトークンの状態を変更することなく、フィンガープリントを変更できる可能性があります。
これにより、いくつかのプロトコルの信頼性が損なわれる可能性があります。
たとえば、トークン所有者がトークンの売買オファーを作成するプロトコルでは、トークンの状態が変わる前に状態を変更することができ、信頼性の前提を崩す可能性があります。

# 引用

Naim Ashhab (@ashhanai), "ERC-5646: Token State Fingerprint," Ethereum Improvement Proposals, no. 5646, September 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5646.

# 最後に

今回は「トークンの実装や仕組みを知らなくても、トークンの状態を追跡できるインターフェースを提案している**ERC5646**」についてまとめてきました！
いかがだったでしょうか？

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

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