[ERC6806] NFTの保有期間を追跡する仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、アドレスごとに**ERC721**形式のNFTの保有期間を追跡するインターフェースを提案している**ERC6806**についてまとめていきます！

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

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

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

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

# 概要

この規格は、**ERC721**規格のNFT（非代替トークン）を拡張して、保持時間を追跡および記述するインターフェースを追加する提案をしています。

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

https://chaldene.net/erc721

### ERC721とは
**ERC721**は、イーサリアムブロックチェーン上でユニークなアイテムを代表するための標準規格です。
これにより、アート作品やコレクターアイテムなど、独自性が保証されたデジタルアセットの所有権を表現できます。
**ERC721**は、各トークンが一意のIDを持ち、それぞれ異なる属性を持つことができる点で、より一般的なトークン標準である**ERC20**とは異なります。

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

https://chaldene.net/erc20

### 保持時間を追跡する拡張機能
この標準によって追加される保持時間の追跡機能は、NFTがどのアカウントにどのくらいの期間保持されているかを記録します。
これは、所有者がトークンをどのくらいの時間保有しているかに基づいて特典を提供したり、トークンの歴史をより透明にするために役立ちます。

### 機能の詳細
- **保持時間の計測**
    - この機能は、特定のNFTが特定のアドレスによって保持されている時間を正確に追跡します。
    - これには、トークンが`transfer`されるたびにタイマーがリセットされることが含まれます。
- **透明性と信頼性の向上**
    - トークンの保持期間がブロックチェーンに記録されることで、そのトークンの歴史が透明になり、コレクターや投資家にとっての信頼性が向上します。
- **プログラマブルな特典**
    - 開発者は、保持時間に基づいて特定の報酬や特典を提供する機能をプログラムできます。
    - 例えば、ある期間以上NFTを保持しているユーザーに対して特別なアクセス権やボーナスを提供することが可能です。

### 応用例
- **アーティストロイヤリティ**
    - 長期間保持されたNFTに対してアーティストが追加のロイヤリティを受け取れるようにする。
- **ゲーム内報酬**
    - あるNFTを一定期間保持することで、ゲーム内で特別な能力やアイテムを解除できる。
- **コミュニティ報酬**
    - コミュニティメンバーがNFTを特定の期間保持していることを条件に、イベントへの招待や限定グッズを提供する。

この標準の拡張により、NFTエコシステムはより多様でインタラクティブなものになり、所有者の行動を奨励する新しい方法が提供されます。
また、このような機能は、デジタルアセットの価値を高め、コミュニティのエンゲージメントを促進する可能性があります。

# 動機

この規格で提案されているのは、**ERC721**標準を拡張し、NFTの保持期間を追跡する機能を含めることです。
これにより、NFTがどれだけの期間特定のアカウントによって保持されているかを知ることが可能になります。
この情報は様々な用途で価値があると考えられます。
具体的には、以下の点で利用価値があります。

### 長期保持者への報酬
長期にわたってNFTを保持するユーザーに対して、特別な報酬やインセンティブを提供することができます。
これは、ユーザーがアセットを保持し続ける動機を与え、市場の安定性に貢献する可能性があります。

### 独占コンテンツへのアクセス権限
NFTの保持期間に基づいて、特定のコンテンツやイベントへのアクセス権を制限することができます。
例えば、ある期間以上特定のNFTを保有しているユーザーのみが参加できるイベントや、特別なデジタルコンテンツにアクセスできるようにすることが可能です。

### 特定のビジネスロジックの実装
NFTの保持時間に基づいて、独自のビジネスロジックやゲーム内ルールを実装することが可能になります。
この機能により、保持期間に応じてユーザーのステータスを変更したり、特定のアクションをトリガーすることができます。

### 提案の目的
現行の**ERC721**標準では、NFTの保持時間を直接追跡する内蔵メカニズムがありません。
この提案は、その限界を克服しようとするもので、NFTの保持期間を追跡する機能を**ERC721**に統合することを目指しています。
これにより、開発者は保持時間に基づく新しい機能や報酬システムを容易に導入できるようになります。

### 実装の考慮点
- **透明性とセキュリティ**
    - 保持時間の追跡と記録は、ブロックチェーンの透明性とセキュリティの原則に則って行われる必要があります。
- **スマートコントラクトの拡張**
    - **ERC721**の既存のスマートコントラクトを拡張し、保持時間を追跡するロジックを組み込むことになります。
- **ユーザーインタフェースの調整**
    - 保持時間に基づく機能を提供する場合、ユーザーにとってわかりやすいインタフェースが必要です。

この提案によって、NFTエコシステムはさらに多様化し、ユーザーのエンゲージメントを深める新たな方法が提供されることになります。
また、長期的な保持を促進することで、デジタルアセットの価値や市場の安定性を高める効果が期待できます。

# 仕様

## インターフェース

以下のインターフェースは、既存の**ERC721**規格を拡張したものです。

```ts
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0

interface IERC6806 {
    function getHoldingInfo(
        uint256 tokenId
    ) external view returns (address holder, uint256 holdingTime);
}
```

提案された `getHoldingInfo` 関数は、特定のNFTに関する2つの重要な情報を提供するものです。
この関数は、**ERC721**拡張機能の一環として提案されており、NFTの現在の保持者とその保持期間（秒単位）を返します。
これにより、NFTの保持履歴の透明性が高まり、開発者はより洗練されたアプリケーションや機能を構築できるようになります。
関数の入力と出力を見ていきます。

### 入力
- **tokenId**
    - これは、問い合わせを行うNFTの一意の識別子です。
    - **ERC721**規格において、各NFTは一意のIDによって区別されます。
    - このIDを使って、特定のNFTに関する情報を取得します。

### 出力
- **holder**
    - この値は、NFTの現在の保持者のアドレスを表します。
    - これは、NFTがどのイーサリアムアドレスによって現在保持されているかを示します。
- **holdingTime**
    - これは、現在の保持者がそのNFTを保持している期間を秒単位で表したものです。
    - この時間は、NFTが現在の所有者に`transfer`されてからの経過時間として計算されます。

### 機能の利点
この関数により、NFTの所有履歴に関する透明性が向上し、NFTエコシステム内での信頼性が高まります。
例えば、長期保有者に報酬を提供するようなプログラムや、特定の保持期間が必要なアクセス権限の設定など、さまざまな用途でこの情報を活用することが可能です。

また、この機能は、NFTの価値を高め、コミュニティに貢献しているユーザーを報酬する新しい方法を提供します。
このようにして、NFTの保持期間を基にした様々なビジネスロジックやゲームメカニクスの実装が可能になります。

# 補足

提案された `getHoldingInfo` 関数を**ERC721**標準の拡張機能として追加することにより、開発者は保持時間情報が必要なNFTベースのアプリケーションを実装できるようになります。
この拡張は、既存の**ERC721**実装との互換性を維持しつつ、新しいユースケースに対する追加機能を提供します。

### `getHoldingInfo` 関数の重要性

- **直感的な方法での情報取得**
    - `getHoldingInfo` 関数は、NFTの保持時間と保持者アドレスを取得するための直接的な方法を提供します。
    - 秒単位で保持期間を表現することで、スマートコントラクト内の他の時間ベースの機能との互換性を確保し、精度を高めています。
  
- **複雑なケースの処理**
    - この関数は、保持者(`holder`)と保持時間(`holdingTime`)の両方を返します。
    - これにより、実装によっては、保持時間の計算から特定のトークン所有者を無視することが可能になります。
    - 例えば、NFTがローンの担保としてコントラクトに所有されている場合、そのようなローンコントラクトは無視され、実際の所有者の保持時間が適切に増加します。

### 応用例

- **ローンコントラクトと担保**
    - あるユーザーがローンを受けるためにNFTを担保として提供した場合、そのNFTはローンコントラクト（または担保を保持するコントラクト）に一時的に所有されます。
    - この期間中にも、実際の所有者の保持時間は中断されずにカウントされ続けるべきです。
    - `getHoldingInfo` 関数は、このような状況を考慮し、実際の所有者の保持時間を正確に追跡することを可能にします。

- **報酬システム**
    - 開発者は、保持時間に基づいてユーザーに報酬を提供するシステムを構築することができます。
    - 例えば、特定の期間以上NFTを保持しているユーザーに対して追加の報酬を提供することが可能になります。

この拡張機能により、**ERC721**ベースのNFTの使用範囲が広がり、より多様でインタラクティブなアプリケーションやエコシステムの開発が可能になります。
また、NFTの保持時間を基にした新しいインセンティブメカニズムの創出にも寄与します。

# 互換性

この規格は、既存の**ERC721**標準と完全に後方互換性を持ちます。
つまり、**ERC721**標準に新しい機能を追加しても、標準の核となる機能には影響を与えません。

# 実装

```ts
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./IERC6806.sol";

contract ERC6806 is ERC721, Ownable, IERC6806 {
    mapping(uint256 => address) private _holder;
    mapping(uint256 => uint256) private _holdStart;
    mapping(address => bool) private _holdingTimeWhitelist;

    constructor(
        string memory name_,
        string memory symbol_
    ) ERC721(name_, symbol_) {}

    function _afterTokenTransfer(
        address from,
        address to,
        uint256 firstotTokenId,
        uint256
    ) internal override {
        if (_holdingTimeWhitelist[from] || _holdingTimeWhitelist[to]) {
            return;
        }

        if (_holder[firstotTokenId] != to) {
            _holder[firstotTokenId] = to;
            _holdStart[firstotTokenId] = block.timestamp;
        }
    }

    function getHoldingInfo(
        uint256 tokenId
    ) public view returns (address holder, uint256 holdingTime) {
        return (_holder[tokenId], block.timestamp - _holdStart[tokenId]);
    }

    function setHoldingTimeWhitelistedAddress(
        address account,
        bool ignoreReset
    ) public onlyOwner {
        _holdingTimeWhitelist[account] = ignoreReset;
        emit HoldingTimeWhitelistSet(account, ignoreReset);
    }
}
```

### `_afterTokenTransfer`

```javascript
function _afterTokenTransfer(
    address from,
    address to,
    uint256 firstotTokenId,
    uint256
) internal override {
    if (_holdingTimeWhitelist[from] || _holdingTimeWhitelist[to]) {
        return;
    }

    if (_holder[firstotTokenId] != to) {
        _holder[firstotTokenId] = to;
        _holdStart[firstotTokenId] = block.timestamp;
    }
}
```

**概要**

トークンが転送された後に自動的に呼び出される内部関数。

**詳細**

この関数は、トークンの`transfer`が行われるたびに**ERC721**コントラクトから自動的に呼び出されます。
保持時間の追跡を開始するために、`transfer`先アドレスと現在のタイムスタンプを記録します。
ただし、`transfer`元または`transfer`先アドレスが保持時間計算のホワイトリストに含まれている場合は、この処理をスキップします。

**引数**

- `from`
    - トークン`transfer`元アドレス。
- `to`
    - トークンの`transfer`先アドレス。
- `firstotTokenId`
    - `transfer`されるトークンのID。

### `getHoldingInfo`

```javascript
function getHoldingInfo(
    uint256 tokenId
) public view returns (address holder, uint256 holdingTime) {
    return (_holder[tokenId], block.timestamp - _holdStart[tokenId]);
}
```

**概要**

指定されたトークンIDに対する保持者とその保持期間を取得する関数。

**詳細**

この関数は指定されたトークンの現在の保持者アドレスと、そのトークンが現在の保持者によって保持されている期間（秒単位）を返します。
保持期間は、トークンが最後に転送されてからの時間として計算されます。

**引数**

- `tokenId`
    - 情報を取得したいトークンのID。

**戻り値**

- `holder`
    - トークンの現在の保持者のアドレス。
- `holdingTime`
    - トークンが現在の保持者によって保持されている期間（秒単位）。

### `setHoldingTimeWhitelistedAddress`

```javascript
function setHoldingTimeWhitelistedAddress(
    address account,
    bool ignoreReset
) public onlyOwner {
    _holdingTimeWhitelist[account] = ignoreReset;
    emit HoldingTimeWhitelistSet(account, ignoreReset);
}
```

**概要**

特定のアドレスを保持時間計算のホワイトリストに追加または削除する関数。

**詳細**

この関数を使用すると、コントラクトの所有者は特定のアドレスを保持時間の計算から除外するか、またはその計算に含める設定を行うことができます。
これにより、例えば、ローンコントラクトなど特定のアドレスによる保持期間のリセットを無視できるようになります。

**引数**

- `account`
    - ホワイトリストに追加または削除したいアドレス。
- `ignoreReset`
    - このアドレスによる保持時間リセットを無視する場合は`true`、そうでなければ`false`。

# セキュリティ

このEIP（Ethereum Improvement Proposal）の導入により、保持時間を追跡するための追加的な状態管理が必要になりますが、これにはセキュリティ上の影響が伴う可能性があります。
実装者は、特に`transfer`時に保持時間の操作に関連する潜在的な脆弱性に注意する必要があります。

## 潜在的な攻撃ベクトルとセキュリティのベストプラクティス

### 再入性攻撃（Reentrancy Attacks）
スマートコントラクトが他のコントラクトを呼び出し、その呼び出しを利用して初期の関数呼び出しを再度実行させる攻撃です。
保持時間の管理ロジックが再入性攻撃に弱い場合、攻撃者は保持時間のデータを不正に操作する可能性があります。

### フロントランニング攻撃（Front-Running Attacks）
ブロックチェーン上での取引を監視し、有利な取引を先回りして実行する攻撃です。
例えば、トークンの`transfer`が予定されていることを検知した攻撃者が、その`transfer`を先回りして自身の取引を挟み込むことで保持時間の計算に影響を与える可能性があります。

### スマートコントラクトのセキュリティベストプラクティス
開発者は、これらの攻撃ベクトルに注意を払いつつ、スマートコントラクトの一般的なセキュリティベストプラクティスに従って設計、実装する必要があります。
これには、コードの十分なテストとレビューが含まれ、実装の安全性と正確性を確保するために必要です。

## ガスコストと最適化
保持時間情報の維持と更新はガスコストを発生させるため、開発者はコントラクト実行コストへの影響を最小限に抑えるための最適化を検討する必要があります。
これには、状態変更の効率化や、不必要なデータの記録を避けることなどが含まれます。

## ブロックタイムスタンプの精度
保持時間情報の精度は、基盤となるブロックチェーンのタイムスタンプの精度に依存します。
ブロックタイムスタンプは一般に信頼できるものですが、マイナーによってある程度操作される可能性があります。
そのため、絶対的な精度が求められる状況では、保持時間データを唯一の真実の源として依存すべきではありません。

:::note info
このEIPを実装する時には、潜在的なセキュリティリスクに注意し、スマートコントラクトの設計と実装におけるベストプラクティスに従い、適切なテストとコードレビューを行うことが重要です。
また、ガスコストの管理と最適化にも留意し、保持時間データの使用に当たっては、その限界を理解する必要があります。
:::

# 引用

Saitama (@saitama2009), Combo <combo@1combo.io>, Luigi <luigi@1combo.io>, "ERC-6806: ERC-721 Holding Time Tracking [DRAFT]," Ethereum Improvement Proposals, no. 6806, March 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6806.

# 最後に

今回は「アドレスごとに**ERC721**形式のNFTの保有期間を追跡するインターフェースを提案している**ERC6806**」についてまとめてきました！
いかがだったでしょうか？

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

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

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

https://chaldene.net/

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

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