はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

今回は、NFTの所有者がトークンに付随する32バイトのデータを更新できる仕組みを提案しているERC1948についてまとめていきます!

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

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

概要

NFT(非代替性トークン)では、通常、生成時(ミント時)に一度だけ設定される静的なメタデータしか扱えません。 ERC1948では、トークンに 32 バイトの可変データ領域を追加し、トークン所有者のみが後からその領域を書き換えられる仕組みを導入します。 可変データを読み取るための関数と、所有者による更新を許可する関数をコントラクトに実装し、更新時にはデータの古い値と新しい値を記録したイベントを発行します。

動機

既存のメタデータ標準では、トークンが持つ情報はミント時にのみ設定され、後から変更できないため、動的に変化する情報を扱えません。 例えば、ブリーディング(交配)によって色や形状が変わるクリプトキティーズ、権利保有者をリアルタイムで更新する知的財産トークン、サイドチェーンやプラズマチェーンとのブリッジング時に状態を運搬するためのデータなど、多彩なユースケースが存在します。 これらの用途を実現するには、トークンのライフサイクルにわたって情報を書き換えられる仕組みが不可欠です。 ERC1948では、そうした動的データを標準化し、NFTを活用する各種プロトコルやブリッジとの相互運用性を高めることを目的としています。

仕様

IERC1948.sol
pragma solidity ^0.5.2;

/**
 * @dev Interface of the ERC1948 contract.
 */
interface IERC1948 {

  /**
   * @dev Emitted when `oldData` is replaced with `newData` in storage of `tokenId`.
   *
   * Note that `oldData` or `newData` may be empty bytes.
   */
  event DataUpdated(uint256 indexed tokenId, bytes32 oldData, bytes32 newData);

  /**
   * @dev Reads the data of a specified token. Returns the current data in
   * storage of `tokenId`.
   *
   * @param tokenId The token to read the data off.
   *
   * @return A bytes32 representing the current data stored in the token.
   */
  function readData(uint256 tokenId) external view returns (bytes32);

  /**
   * @dev Updates the data of a specified token. Writes `newData` into storage
   * of `tokenId`.
   *
   * @param tokenId The token to write data to.
   * @param newData The data to be written to the token.
   *
   * Emits a `DataUpdated` event.
   */
  function writeData(uint256 tokenId, bytes32 newData) external;

}

DataUpdated

javascript
event DataUpdated(uint256 indexed tokenId, bytes32 oldData, bytes32 newData);

データが更新された時に発行されるイベント。 指定したトークンのデータ領域が新しい値に書き換えられる度に発行されます。 トークンの所有者が writeData を呼び出してデータを更新すると、その前後のデータを含む情報がブロックチェーン上に記録され、外部で履歴を追跡可能になります。

パラメータ

  • tokenId
    • 更新対象のトークンID。
  • oldData
    • 更新前に格納されていた32バイトデータ。
  • newData
    • 更新後に格納された32バイトデータ。

readData

javascript
function readData(uint256 tokenId) external view returns (bytes32);

指定したトークンから現在のデータを読み取る関数。 トークンIDに対応するデータ領域を読み込み、現在格納されている32バイトの値を返します。 呼び出しは外部からの参照のみで、チェーン上の状態を変更しません。

引数

  • tokenId
    • データを読み取る対象のトークンID。

戻り値

  • bytes32
    • 指定したトークンに格納されている現在の32バイトデータ。

writeData

javascript
function writeData(uint256 tokenId, bytes32 newData) external;

指定したトークンのデータを更新する関数。 呼び出したアドレスがトークンの所有者である場合にのみ、データ領域を書き換えます。 更新前後の値は DataUpdated イベントとして発行され、変更履歴を外部に通知します。

引数

  • tokenId
    • データを更新する対象のトークンID。
  • newData
    • 書き込む新しい32バイトデータ。

補足

ERC1948で追加される32バイトのデータ領域は、トークン自身に直接格納できるシンプルな情報(カウンターやアドレスなど)を想定しています。 これ以上の複雑なデータ構造が必要な場合は、MerkleツリーやPatriciaツリーといった認証付きデータ構造を併用することを推奨します。 もともと分散型のブリーディング(繁殖)システムとの連携を念頭に置いた提案であり、xDAI、Skale、POA、LeapDAO などのサイドチェーン実装との相互運用性を高める狙いがあります。

テスト

テストではまず、新規デプロイされたコントラクトにトークンをミントし、初期状態ではデータ領域がすべてゼロ(32 バイトの空データ)であることを検証します。 続いて writeData を呼び出して任意のデータを書き込み、再度 readData で読み出すことで、書き込んだ値が正しく格納されていることを確認します。 この流れは「読み取り→書き込み→再読み取り」という単純なパスを通じて、コントラクトの基本的な動作が期待どおりであることを示します。

js
const ERC1948 = artifacts.require('./ERC1948.sol');

contract('ERC1948', (accounts) => {
  const firstTokenId = 100;
  const empty = '0x0000000000000000000000000000000000000000000000000000000000000000';
  const data = '0x0101010101010101010101010101010101010101010101010101010101010101';
  let dataToken;

  beforeEach(async () => {
    dataToken = await ERC1948.new();
    await dataToken.mint(accounts[0], firstTokenId);
  });

  it('should allow to write and read', async () => {
    let rsp = await dataToken.readData(firstTokenId);
    assert.equal(rsp, empty);
    await dataToken.writeData(firstTokenId, data);
    rsp = await dataToken.readData(firstTokenId);
    assert.equal(rsp, data);
  });

});

参考実装

ERC1948.sol
/**
 * @dev Implementation of ERC721 token and the `IERC1948` interface.
 *
 * ERC1948 is a non-fungible token (NFT) extended with the ability to store
 * dynamic data. The data is a bytes32 field for each tokenId. If 32 bytes
 * do not suffice to store the data, an authenticated data structure (hash or
 * merkle tree) shall be used.
 */
contract ERC1948 is IERC1948, ERC721 {

  mapping(uint256 => bytes32) data;

  /**
   * @dev See `IERC1948.readData`.
   *
   * Requirements:
   *
   * - `tokenId` needs to exist.
   */
  function readData(uint256 tokenId) external view returns (bytes32) {
    require(_exists(tokenId));
    return data[tokenId];
  }

  /**
   * @dev See `IERC1948.writeData`.
   *
   * Requirements:
   *
   * - `msg.sender` needs to be owner of `tokenId`.
   */
  function writeData(uint256 tokenId, bytes32 newData) external {
    require(msg.sender == ownerOf(tokenId));
    emit DataUpdated(tokenId, data[tokenId], newData);
    data[tokenId] = newData;
  }

}

引用

Johann Barbie (@johannbarbie), Ben Bollen ben@ost.com, pinkiebell (@pinkiebell), "ERC-1948: Non-fungible Data Token [DRAFT]," Ethereum Improvement Proposals, no. 1948, April 2019. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1948.

最後に

今回は「NFTの所有者がトークンに付随する32バイトのデータを更新できる仕組みを提案しているERC1948」についてまとめてきました! いかがだったでしょうか?

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

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