ERC721をEIPやIERCの違いなどから丁寧に学んでみよう!
こんにちは、CryptoGamesのユウキです。
こちらは、2023年2月15日(水)に予定しています、PalAcademyでの勉強会の資料になります。
テーマは「ERC721について」です。
1 EIPとERCはどう違うの?
ERCを理解するために、まずはEIPとは何かを学んでみましょう。
EIPは「Ethereum Improvement Proposals」の略で、その名の通り、イーサリアムを改善するための提案になります。
下のように、提案の現在の状況を確認でき、「Draft」や「Final」などがあります。
そして、一言で「提案」といっても、いろいろな種類の提案があります。
コアな部分の提案やネットワークに関する提案などがあります。
その中でアプリレベルの提案が「ERC」になります。
つまり、EIPとERCの関係性はこのようになっています。(こちらの図は訂正したものです。)
ERCとはイーサリアムの改善の提案のうちの一つの種類なのですね。
2 ERCを見てみよう
では、ERCを見てみましょう。
「Final」となっているERCなど、ステータス別に確認ができます。
そして、「ERC721」もこの中の一つです。
「Non-Fungible Token Standard」と書かれています。
いわゆる、NFTの標準であるということがわかりました。
https://eips.ethereum.org/erc
3 ERC721をみてみよう
では、ERC721を見てみましょう。
「Simple Summary」を見てみると、標準的なインターフェースと書いてあります。
https://eips.ethereum.org/EIPS/eip-721
1) インターフェースとは
ちなみに、「インターフェース」とはなんでしょう?
ググると、こんな感じになっています。
① お部屋の電気の例(入力なし)
では、部屋の電気の例で考えてみましょう。
電気のボタンと電気があります。
ボタンを押すと、このように、電気が光ります。
でも、私たちは具体的に中の実装がどうなっているかを知りません。(なんとなくはわかるかもですが。)
電気をつけたいだけなので、中身について知る必要がないですし、興味もありません。
つまり、私たちは何をしたときに、どういう結果が返ってくるのかを知れれば良いです。
これがインターフェースを考える上で大切です。
② パスワード付きの扉の例(入力あり)
何か、入力がある場合も全く同じですね。
これはパスワード付きの扉の場合です。
こちらも、パスワードを入力してボタンを押せば扉が開くということさえわかれば利用することができます。
中の実装について知る必要はありません。
2) トランスファーのインターフェースの概要
では、ERC721で考えてみたいと思います。
下のように、何かを入力して、トランスファーボタンを押すイメージです。
今回であれば、下の3つの情報を入れて、ボタンを押せばトランスファーがされれば良いです。
- ①誰から?(_from)
- ②誰に?(_to)
- ③何を?(_tokenId)
そして、インターフェースである以上、実装のことにはあまり触れられません。
何をしたら、どうなるのかということが書かれています。
3) トランスファーのインターフェースを見てみよう!
では、実際のものを見てみましょう。
一見難しそうですが、ただの英語です。
この関数が、どのように動くのかや、入力値に対する説明などが書かれています。
https://eips.ethereum.org/EIPS/eip-721
4) インターフェースの名前は固定?
次は、関数などの名前についても考えてみましょう。
インターフェースには名前も設定されていますが、もし各々が好きな名前を設定できたらどうでしょう?
すると、それぞれが同じ趣旨の関数なのか、いちいち中身を確認しなくてはいけません。
だからこそ、インターフェースで関数の名前を決めることは非常に重要です。
5) ERC721を具体的に見てみよう!
では、以上を踏まえて、ERC721を見てみましょう。
結論としては、このような構成になっています。
では、実際に見てみましょう。
まずはEventと呼ばれるものが3つあります。(今日は詳しく扱いません)
そして、下のように、safeTransferFromなどのTransfer系があります。
最後に、別のアドレスに送付の承認権限を与えるApprove系があります。
4 OpenZeppelinとは
1) 実装について考えてみよう
では、具体的な実装を考えてみましょう。
入出力が決まっているので、このはてなの中身を考える作業になります。
この中身はこのようになっていました。
簡単ですね。
では、次はこちらを考えてみましょう。
今回は新人のAさんに実装をお願いしました。
このような中身になりました。
ちょっと皆さんのイメージと違うと思います。
でも、確かにうまくいきそうな気もします。
それでもちょっと腑に落ちないですよね。
ちなみに、0を入れると、エラーになってしまいます。
0で割ることってできないですもんね。
ちょっと大げさな例だったかも知れませんが、エンジニアも一人の人間なので、実装の際には知識不足やミスから同じことが起きかねません。
そんな時、模範的な実装が示されていたら安心ですね。
それを提供してくれるのがOpenZeppelinです!
2) OpenZeppelinを見てみよう!
① ファイルの場所を見てみよう
では、OpenZeppelinを見てみましょう。
ここにERC721がありますね。
https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token
さらに中に入ると、「ERC721.sol」というファイルがありました。
こちらが実際の実装しているファイルになります。
また「.sol」は「solidity」で書かれたファイルであることを表しています。
https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC721
ちなみに、その下に、似た名前の「IERC721.sol」というファイルもあります。
この「I」は「Interface」です。
つまり、EIPの721で定義されていたインターフェースがこちらに格納されています。
https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC721
② ファイルの中身を見てみよう
では、中身を見てみましょう。
このように始まっています。
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol
さらに見てみると、「contract」から始まる部分に「ERC721」と書かれています。
ここからコントラクトが始まっています。
先ほど出てきた、「balanceOf」や「ownerOf」も出てきました。
確かに何やら実装しています。
③ あれ?そういえば、ミントは??
そして、「_mint」という関数も出てきます。
よく考えると、ミントはEIPの721にはありませんでしたね。
https://eips.ethereum.org/EIPS/eip-721
そのため、OpenZeppelinで独自に実装してくれています。
burnも実装してくれています。
このようにして、OpenZeppelinのファイルを確認していくことができます。
5 ミントやバーン、トランスファーについて学ぼう
では、今回は、ミントやバーン、トランスファーに焦点を絞って学んでみましょう。
1) mappingとは?
まずは、重要な概念である、mapping(と配列)について見ていきましょう。
配列は0から始まる番号がついている箱です。
一方、mappingは箱に自由な名前を付けることができます。
2) 重要な2つのmapping
では、OpenZeppelinのERC721に出てくる重要なmappingを見てみましょう。
「_owners」によって、特定のtokenIDの所有者がわかります。
そして、「_balances」によって、誰がいくつトークンを持っているかを知ることができます。
実は、ミントなどの処理はこれらのmappingを利用しているだけなんです。
3) ミントについて
では、まずミントについて考えていきましょう。
太郎くんが既にNFTを5個持っており、tokenID:8を太郎くんにミントするケースを考えてみます。
「_owners」については、tokenID:8を太郎くんにします。
また、太郎くんのNFTが一つ増えたので、「_balances」は一つ増やせば良いでしょう。
なんと、ミントの本質は、たったのこれだけです。
ちょっと拍子抜けかも知れませんね。
4) バーンについて
では、バーンも見てみましょう。
太郎くんが既にNFTを6個持っており、太郎くんが持っているtokenID:8をバーンするケースを考えてみます。
「_owners」については、tokenID:8を太郎くんから0アドレスに変更します。
また、太郎くんのNFTが一つ減ったので、「_balances」は一つ減らせば良いでしょう。
やっていることはミントとほとんど同じですね。
5) トランスファーについて
トランスファーについても考えていきましょう。
tokenID:8を太郎くんから花子さんに送るケースを考えてみましょう。
太郎くんはもともとNFTを6個、花子さんは3個持っているとしましょう。
「_owners」については、tokenID:8を太郎くんから花子さんに変更します。
また、太郎くんのNFTが一つ減ったので、「_balances」は一つ減らせば良いでしょう。
一方、花子さんのNFTが一つ増えたので、「_balances」は一つ増やせば良いでしょう。
ミント、バーンとやっていることはほとんど同じですね。
6) 前提条件チェック(require)について
次は、前提条件チェック(require)を見ていきましょう。
例えば、トークンID:8をミントする前に、そもそも「まだミントされていない」ことを事前にチェックする必要があります。
これらのチェックを行うのがrequireです。
具体的に、コードを見てみると、こんな風になっています
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol
よかったら、トランスファーについても想像してみてください。
こんな風になっています。
7) イベントについて
最後にイベントについてです。
イベントを発火することによって、現在の状態をアプリに通知したり、検索がしやすくなります。
例えば、これがAzukiをTransferというイベントで検索した結果です。
https://etherscan.io/address/0xed5af388653567af2f388e6224dc7c4b3241c544#events
① トランスファーのイベントについて
では、具体的に、トランスファーのイベントを見てみましょう。
このように、「①誰から」「②誰に」「③何を」送ったのかを記録します。
② ミントとバーンのイベントについて
では、ミントやバーンはどんなイベントを発火するのでしょうか?
実は、なんとトランスファーです!
ミントは結局、「0アドレスから」変えるものであり、バーンは結局、「0アドレスへ」変えるものだからです。
コードを見てみると、確かに、ミントでトランスファーイベントを発火していることがわかります。
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol
今回は以上です。
なお、今回説明しきれなかった、Approveはこちらで動画にしています。
よかったらご参照ください。
Discussion