第3回 solidity学習会 講義ノート(3/5 AM8:00~)
こんにちは、CryptoGamesの高橋です。
クリスペの会社です。
また、CryptoMaidsのアンバサダーも務めさせていただいております。
今回は、3/5 AM8:00から次の場所で勉強会を行いますので、その講義ノートの公開です。
場所 https://meet.google.com/zme-pohq-hcm
予習や復習などに役立てていただければ幸いです。
はじめる前に
・実施はテストアカウントで行うことを推奨いたします。
Rinkebyというテストネットで実施を行いますが、操作を誤ってしまったときに実際のETHが使われないためです。
・シークレットNFTを行いますが、技術の話と法律面の話は別物だ考えています。今回はあくまでも、技術的にどのように実現するかというのみの話になります。
0 準備
第1回と同様のコードを利用します。
こちらの記事を参考に、Remixにコードを貼り付けてそれを元に見て行きましょう。(ここを省いても記事は読めます。)
1 その関数、見つかりますか?
こちらのEtherscanを元に見ていきます。
https://rinkeby.etherscan.io/address/0xa0092d257278d5d7cb2a247091aed51e69d0179f#code
「Read Contract」で下の「balanceOf」という関数を見てみましょう。
この関数の内容は、指定したウォレットアドレスがいくつのアセット(NFT)を持っているのかを返します。
今回の例の場合、「0x961aEa560D4C38B08D9300D8219062Fe853c8315」は3つのNFTを持っていることがわかります。
便利ですね。
一方、この「balanceOf」という関数は「Purple.sol」の中のどこで動作を定義しているでしょうか?
「ctrl」+「F」で検索窓が出てきますので、検索してみて下さい。
すると、利用している箇所は見つかったものの、定義している部分は見つからなそうです。
他にも、下のような関数を探してみて下さい。(変数+ getter関数という場合もありますが、今回その辺は省略)
やはりなさそうです。
不思議ですね。どうやって使えるようになっているのでしょう。
2 継承について
2ー1 継承とは
ここで出てくるのが「継承」です。
他のコントラクトのコードを利用することができます。
こんな形になっていますね。
contract 継承先 is 継承元 {
・・・
}
継承により、「ERC721Enumerable」、「Ownable」を「PurpleEye」コントラクトで利用できるようになります。
図にするとこんな感じです。
実はこれにより、前回「onlyOwner」を使えるようになりました。
2ー2 具体的な継承の例を見てみよう
今回のコントラクトは「ERC721Enumerable」コントラクトを継承していました。
一方、「ERC721Enumerable」自身も、下のように継承を行っています。
このように、「ERC721Enumerable」も「ERC721」を継承していたのですね。
これによって、ERC 721で定義された関数などもPurpleEyeで使えるようになりました。
2ー3 ERC721を読んでみよう
では、実際によく聞く「ERC721」を見てみましょう。
まずは、下のように、コントラクトの説明である、コメント部分を読んでみましょう。
先ほど出てきた、Enumerable(内容は後で)については、含まれないと書いてあります。
だからこそ、Enumerableを利用したい場合はEnumerableを継承する必要があることがわかります。
では、ERC721の中身も見てみましょう。
具体的な関数の説明は省きますが、ここで
・balanceOf()
⇨いくつのアセット(NFT)を持っているか
・ownerOf()
⇨そのトークンIDの所有者は誰か
などが定義されているからこそ、「PurpleEye」でこれらを継承し、関数として使えるようになっていたのですね。
2ー4 ERC721Enumerableを読んでみよう
では、ERC721に含まれていない、「ERC721Enumerable」についても見てみましょう。
早速コメントを読んでみましょう。
トークンIDを数えられるようにする、、とのことですが、イメージが湧きにくいので、実際に見てみましょう。
このように、「何番目に何が入っているか」「全部でいくつあるのか」ということがわかるようになります。
2−5 Enumerableがあるとどうなるの?
これは、こちらの「Ryo Manzoku」さんの説明資料がわかりやすいです。
よかったら、ぜひ見てみてください。
今回は詳細を省きますが、Enumerableがあることで列挙を行えるようになります。
(「Ryo Manzoku」さんの上の資料から抜粋)
一方、ガス代がプラスにかかることになります。
なお、このガス代がかかるタイミングを最適化しようとしているのがAzukiのERC721Aというようにつながっていきます。
こちらもmanzokuさんの記事が分かりやすいと思います。
3 overrideについて
3ー1 tokenURIを見てみる
では、少し戻り、ERC721の中身を見てみましょう。
下のように「tokenURI」という関数が定義されています。
これにより、ERC721で定義された「tokenURI」という関数は、「ERC721Enumerable」に継承され、さらに「PurpleEye」コントラクトに継承されています。
3ー2 オーバーライドによる再定義
上のように、「tokenURI」という関数がERC721で定義され、「PurpleEye」コントラクトで使用ができます。
でもちょっと関数をカスタマイズしたい時があります。
例えば今回は、シークレット機能として、NFTの公開前後によって、指定するトークンURIを変えたいとします。
そのために、関数を再定義するのが「override」です。
ERC721で定義されたtokenURI()という関数がPurpleEyeでも定義されています。
これにより、関数がoverride(上書きのイメージ)されることになります。
今回はざっくりと「if文」や「baseExtension」の機能を追加しているようです。(詳細は省きます。)
3ー3 virtualについて
このように「override」によって、関数の再定義ができるのですが、再定義をされたくない関数も当然存在します。
むしろその方が多そうですね。
そのため、「virtual」がついている関数だけがoverride(関数の再定義)が可能になっています。
ERC721の「tokenURI」を見てみると、確かに、「virtual」がついていましたね。
4 payable(関数)
関数に「payable」の修飾子が付くとEtherを受け取ることができる関数になります。
この関数はミント時にEtherを受け取っていそうです。(詳細は省きます。)
5 require文
関数を実行するための前提条件を確認するのがrequire文です。
括弧の中が正しくなければ(trueでなければ)、関数は実行されません。
例えば「_mintAmount >0」はミントする数が0以下でしたら、ミントができませんね。
ちなみに、require文はかつては引数が一つでしたが、現在では上のように「,」で区切って、エラー時の文言を書くようになっています。
6 ミントをできないようにする(pausedの利用)
一定の期日が来るまで、ミントができないようにする、などの実際の処理を見てみましょう。
下のようにbool型(○か×の2択の)pausedという変数を用意します。
下のrequire文を見てみましょう。
仮にpausedにtrueが入っていたとします。「!」は「否定」を意味するので「!paused」は「false」になります。
ということは、require文の括弧の中身が trueではないため、前提条件が満たされず、関数が実行されないことになります。
つまり、pausedをtrueに設定すれば、ミントは行われないことになります。
実際に、pausedをtrueにして、ミントができないことを確認してみてください。
7 msg.sender
Solidityにはコントラクト内で宣言していなくても使用できるグローバル変数が存在します。
その一つがmsg.senderです。
msg.senderは関数を呼び出したアドレスを示します。
例えば、この関数を私(0x96....315)が使用した場合、msg.senderは(0x96....315)になります。
他にもよく使うものとしては、下のようなものがあります。
① msg.value
トランザクションに関する送金したwei
② block.number
現在のブロックナンバー
③ block.timestamp
ブロックが作られたときのタイムスタンプ
8 Ownableコントラクトを読んでみよう
上で出てきた、 owner()関数を見てみましょう。
Ownable.solを見ると、このようにあります。
また、せっかくなので、このコントラクトのコメントも読んでみましょう。
例えば、読んでみると、コントラクトをデプロイしたアカウントがownerであるということもわかります。
9 return構文
9ー1 はじめに
まずは、次の2つの関数を見てみましょう。
① tokenURI
こちらはtokenIdを入れて「Query」を押すと、tokenURIの値が返ってきます。
② setCost
こちらはcostの値を入れて「write」を押すと、costはセットされますが、特に何も返ってきません。
このように、関数には、値が返ってくるものと返ってこないものがあります。
9ー2 return構文を見てみよう
owner()関数に返り値があったので、見てみましょう。
その名の通り、現在のオーナーのアドレスが返ってくるようです。
「return」「returns」となっていることにご注意ください。
また、「address」は「int」が「0以上の整数」の型を表したように「アドレス」の型のものを返すということを記載しています。
10 アドレス型について
せっかくなので、アドレス型についても見てみましょう。
アドレス型には次の2つのアカウントのアドレスがあります。
① 外部アカウント(EOA)
⇨いわゆるウォレット
② コントラクトアカウント
コントラクトアカウントはコードを持ち、秘密鍵は持たないなどの違いがあります。
せっかくなので、公開鍵からウォレットアドレスを生成する方法を見ていきましょう。
秘密鍵⇨公開鍵は複雑なので省略しますが、ご興味がありましたら、まずはこちらを見てみて下さい。
下のように、公開鍵を使い、「ハッシュ値」というものに変換することで、ウォレットアドレスを求めることができます。
参照(https://www.think-self.com/money/cryptocurrency/secp256k1/)
11 if文
if文は丸括弧()の中身が正しければ(trueなら)、波括弧{}の中身を実行します。
上の例であれば、msg.sender(この関数を実行したアドレス)がowner()と一致しなければ、波括弧{}の内容を実行します。
つまり、ミントを実行した人がオーナーでなければ波括弧{}の処理を行うようです。
12 for文
繰り返しの処理を行う場合は、for文を使います。
下の例では、i = 1, i = 2, i = 3の3回処理を実行することになります。
例えばsupply(ここでは、現在のミント総量)が3の時、
i = 1 の時
tokenId : 4(3 + 1) を実行者のアドレスにミント
i = 2の時
tokenId : 5(3 + 2) を実行者のアドレスにミント
i = 3 の時
tokenId : 6(3 + 3) を実行者のアドレスにミント
のようになります。
今回は以上といたします。