[ERC5008] NFTの送付などの状態を管理する仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、**ERC721**規格のNFTに**nonce**というNFTの送付時にインクリメントされる値を取得する関数を追加する提案である**ERC5008**についてまとめていきます！

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

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

:::note warn
**ERC5008**は現在（2023年9月3日）では「**Last Call**」段階です。
:::

# 概要

提案されている規格では、**ERC721**トークンを拡張して「**nonce**」と呼ばれる概念を導入しようとしています。
この「**nonce**」は、一般的にトランザクションの識別子として使われる数字ですが、ここではトークンに関する特定のアクションや変更を追跡するために利用されます。

具体的には、この提案によって「**nonce**」関数が**ERC721**トークンに追加されます。
トークンごとに一意の「**nonce**」が設定され、トークンの状態が変更されるたびに「**nonce**」が増加します。
この関数を呼び出すことで、特定のトークンに関するアクションが何回行われたかや、どの順序で行われたかを確認できるようになります。

この拡張によって、トークンの状態変更や取引の履歴をより正確に追跡できるため、トークンの管理や利用がより透明で効率的になります。

# 動機

NFT（非代替トークン）マーケットプレイスでは出品者が想定しない注文が発生することがあり、NFTが実際の市場価格よりも低い価格で売られる問題が報告されています。
これは、ユーザーがNFTを別のウォレットに送り、後で元のウォレットに戻すことで起こります。
これにより、注文が再び有効になり、オーナーが意図しない低価格でNFTがリストされてしまう可能性があります。

:::message
あるユーザーが自分のNFTを売却するために、マーケットプレイスに注文を出しました。
しかし、その後、ユーザーはNFTを別のウォレットに送り、さらに元のウォレットに戻してしまいました。
この時、もともと出された注文が再度活性化され、予想よりも低い価格でNFTが売られてしまう可能性がります。
:::

この問題に対処するために、EIP（Ethereum Improvement Proposal）と呼ばれる提案が行われています。
この提案では、**ERC721**というトークンのスタンダードに「**nonce**」という新しいプロパティを追加することが提案されています。
そして、トークンが別のウォレットに送られるたびに「**nonce**」が変更されるような仕組みを導入することが提案されています。
もし注文に「**nonce**」が追加されていれば、注文を確認して攻撃を回避することができます。

:::message
具体的には、NFTの所有者がトークンを別のウォレットに送る際に、そのトークンの「**nonce**」が変更されます。
そして、注文に「**nonce**」が関連付けられている場合、トークンが送られたかどうかや、その「**nonce**」の値を確認することで、攻撃を防ぐことができます。
これによって、オーナーが意図しない低価格でNFTが取引される事態を防ぐための対策が提案されています。
:::

この提案によって、NFTマーケットプレイスにおける攻撃や誤った取引を防ぐ手段が提供され、ユーザーや開発者が安心してNFTを取引できる環境が整うことが期待されています。

# 仕様

```javascript
/// @dev the ERC-165 identifier for this interface is 0xce03fdab.
interface IERC5008 /* is IERC165 */ {
    /// @notice Emitted when the `nonce` of an NFT is changed
    event NonceChanged(uint256 tokenId, uint256 nonce);

    /// @notice Get the nonce of an NFT
    /// Throws if `tokenId` is not a valid NFT
    /// @param tokenId The id of the NFT
    /// @return The nonce of the NFT
    function nonce(uint256 tokenId) external view returns(uint256);
}
```

「`nonce(uint256 tokenId)`」という関数が**ERC721**トークンコントラクトに含まれる場合、その関数は他のデータを変更せずに情報を読み取るだけの目的で使われる必要があります。
トークンの特定のIDに対応する「**nonce**」という識別子を返す関数ということがわかります。

「`supportsInterface`」メソッドについての要件を示しています。
このメソッドは、特定の引数（`0xce03fdab`という特定の識別子）を使って呼び出された場合に、必ず「`true`」を返す必要があります。
これは、他のコントラクトやアプリケーションが、このトークンコントラクトが特定の機能をサポートしているかどうかを正確に確認できるようにするためのものです。
この「`supportsInterface`」メソッドは、トークンが特定の標準やプロトコルに従って動作するかどうかを判別する際に使用されます。

# 補足

「`transferCount`」という名前の関数を使ってトークンの処理方法を考えていました。
しかし、後でわかったことは、トークンを単に別の場所に転送するだけでなく、トークンごとの属性が変更されることがあるということです。
例えば、トークンのオーナーが何か重要な属性を変更するケースです。
このような場面では、単にトークンを別の場所に移動するだけでなく、トークンの識別情報（`nonce`と呼ばれる）も変更する必要があります。

そこで、「`transferCount`」という名前では、転送だけを考慮しているため、他の属性が変更される場面に対応できないことがわかりました。
そのため、より一般的で広い意味を持つ「`nonce`」という名前に変更しました。
これによって、トークンが転送されるだけでなく、属性が変更される時にもセキュリティを確保するために`nonce`を変更できるようになります。

# 後方互換性

**ERC721**と互換性があります。

# テスト

以下にコードを格納しています。

https://github.com/cardene777/base-code/blob/develop/contract/test/ERC5008/ERC5008.test.ts

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

# 参考実装

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

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

contract ERC5008 is ERC721, IERC5008 {
    mapping(uint256 => uint256) private _tokenNonce;

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

    /// @notice Get the nonce of an NFT
    /// Throws if `tokenId` is not a valid NFT
    /// @param tokenId The NFT to get the nonce for
    /// @return The nonce of this NFT
    function nonce(uint256 tokenId) public virtual override view returns(uint256) {
        require(_exists(tokenId), "Error: query for nonexistent token");

        return  _tokenNonce[tokenId];
     }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual override{
        super._beforeTokenTransfer(from, to, tokenId);
        _tokenNonce[tokenId]++;
        emit NonceChanged(tokenId, _tokenNonce[tokenId]);
    }

    /// @dev See {IERC165-supportsInterface}.
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC5008).interfaceId || super.supportsInterface(interfaceId);
    }
}
```

# 引用

Anders (@0xanders), Lance (@LanceSnow), Shrug <shrug@emojidao.org>, "ERC-5008: ERC-721 Nonce Extension [DRAFT]," Ethereum Improvement Proposals, no. 5008, April 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5008.

# 最後に

今回は「**ERC721**規格のNFTに**nonce**というNFTの送付時にインクリメントされる値を取得する関数を追加する提案である**ERC5008**」についてまとめてきました！
いかがだったでしょうか？

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

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