2022.10.11
ERC3525 Semi-Fungible Token をソースコードから読み解く
こんにちは。次世代システム研究室のT.M です。
はじめに
先日、ERC3525 が新しいトークン規格として承認されました。ERC3525 はSemi-Fungible Token(SFT)と呼ばれるトークンであり、金融商品などへの応用が期待されているトークンです。本稿では、SFT の仕様を理解するために、EIP で提示されている実装例を読み解きました。
注意
本稿で記述されているコードの一部は、EIP3525 のデモンストレーション用のコードであり、EIP3525 のREADMEに書いてあるように実際のサービスとしての利用に足るコードではないことにご注意ください。
Semi-Fungible Tokenとは
Fungible Token(FT) はERC20 を代表とするトークンであり、代替性トークンと訳されます。全てが同一データの集合であり、通貨としての用途で使われることの多いトークンです。一方、Non-Fungible Token(NFT)はERC721 を代表とするトークンであり、非代替性トークンと訳されます。それぞれが唯一無二のデータの集合であり、アートの所有権管理としての用途で使われることの多いトークンです。
Semi-Fungible Token(SFT)は、半代替性トークンと訳され、それぞれが複数個を許容されたデータの集合であり、金融商品としての応用が期待されているトークンです。SFT はNFT のように各トークンがメタデータに紐づけられており、また、FT のように同一トークンを統合したり、反対に分割をしたりすることができます。
ERC1155 との違い
ERC3525 に似たトークンとして、ERC1155 というトークンがあります。ERC1155 は、NFT において複数個を許容される応用において利用されているトークンです。両者ともメタデータに紐づけられたトークンが複数個存在することができる、という点で同じです。
ERC1155 とERC3525 の異なる点の一つは、柔軟性です。ERC3525 の方が柔軟性が高いです。ERC1155 では、同一メタデータに紐づけられたトークンは全て同じデータですが、ERC3525 では、同一メタデータに紐づけられたトークンはそれぞれ異なるデータになります。そのおかげで、同一トークンであったも異なる価値をつけることができ、金融商品などへの応用に期待されています。
もう一つの異なる点として、ERC3525 はERC721 の拡張である、ということです。つまり、ERC3525 はERC721 でできることが全てできる、ということです。例えば、NFT のマーケットプレイスにERC3525 のトークンを出品することができます。
ERC3525 のソースコード
EIP において、ERC3525 のソースコード例があるので、それをもとにERC3525 の仕様を理解していきます。
データ構造
ERC3525 のデータ構造は以下のように定義されています。
/// @dev tokenId => values mapping(uint256 => uint256) internal _values; /// @dev tokenId => slot mapping(uint256 => uint256) internal _slots;
ERC721 のコードを継承しているので、どのアドレスがどのトークンを所有しているか、という部分が記述されていません。
// Mapping from token ID to owner address mapping(uint256 => address) private _owners;
ERC721 のコードと合わせて読むことで、誰がどのトークンの所有者であるか、そのトークンはいくつあるか、そのトークンのslot(メタデータ)は何か、ということがデータとして保存されていることがわかります。
TransferFrom
TransferFrom には、トークンの移転、分割、統合の3つのケースがあります。
トークンの移転は、ERC721 から継承したものになります。パラメータは、(address _from, address _to, uint32 _tokenId) という形です。ソースコードについては解説を省略します。
トークンの統合は、パラメータは( uint256 fromTokenId_, uint256 toTokenId_, uint256 value_) という形であり、以下のようなコードになっています。
function transferFrom( uint256 fromTokenId_, uint256 toTokenId_, uint256 value_) public payable virtual override { _spendAllowance(_msgSender(), fromTokenId_, value_); _transfer(fromTokenId_, toTokenId_, value_); } function _transfer( uint256 fromTokenId_, uint256 toTokenId_, uint256 value_) internal virtual { require( _exists(fromTokenId_), "ERC35255: transfer from nonexistent token"); require(_exists(toTokenId_), "ERC35255: transfer to nonexistent token"); require( _values[fromTokenId_] >= value_, "ERC3525: transfer amount exceeds balance"); require( _slots[fromTokenId_] == _slots[toTokenId_], "ERC3535: transfer to token with different slot"); address from = ERC721.ownerOf(fromTokenId_); address to = ERC721.ownerOf(toTokenId_); _beforeValueTransfer(from, to, fromTokenId_, toTokenId_, _slots[fromTokenId_], value_); _values[fromTokenId_] -= value_; _values[toTokenId_] += value_; _afterValueTransfer(from, to, fromTokenId_, toTokenId_, _slots[fromTokenId_], value_); emit TransferValue(fromTokenId_, toTokenId_, value_); }
注目すべきは、require( _slots[fromTokenId_] == _slots[toTokenId_], ”ERC3535: transfer to token with different slot”); というコードです。統合先と統合元が同じslot である、ということが統合の条件となっています。
トークンの分割は、パラメータは(uint256 fromTokenId_, uint256 toTokenId_, uint256 value_) という形であり、以下のようなコードになります。
function transferFrom( uint256 fromTokenId_, address to_, uint256 value_) public payable virtual override returns (uint256) { _spendAllowance(_msgSender(), fromTokenId_, value_); uint256 newTokenId = _getNewTokenId(fromTokenId_); _mint(to_, newTokenId, _slots[fromTokenId_]); _transfer(fromTokenId_, newTokenId, value_); return newTokenId; } function _mint( address to_, uint256 tokenId_, uint256 slot_) private { ERC721._mint(to_, tokenId_); _slots[tokenId_] = slot_; emit SlotChanged(tokenId_, 0, slot_); }
特徴的なのはトークンをmint している、というところです。mint の処理では、ERC721 のmint をして、mint されたトークンに分割元のトークンと同一のslot を割り当てます。次に分割する個数だけ分割元のトークンの個数を減らし、mint されたトークンの個数を増やします。こうすることで、分割されたトークンはERC721 トークンでありつつ、また、個数やslot という情報を付与することができます。
slotURI
ERC3525 では、tokenURI の他にslotURI に追加のメタデータであるslot を保存します。tokenURI のソースコードとほぼ同じです。
function slotURI(uint256 slot_) public view virtual override returns (string memory) { string memory baseURI = _baseURI(); return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, "slot/", slot_.toString())) : ""; }
まとめ
NFT とFT の間の存在である、Semi-Fungible Token(SFT)という新しいトークンの概念が生まれました。SFT は金融商品として期待されるトークンです。
SFT の標準仕様としてERC3525 が承認されました。ERC3525 の仕様を理解するために、サンプルコードを読み解きました。
ERC3525 はERC721 を継承したものであるため、扱いやすいトークンとなっています。今後も注目をしていきたいと考えました。
次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。インフラ設計、構築経験者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。
皆さんのご応募をお待ちしています。
グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。
Follow @GMO_RD