CryptoZombiesの続きです。
レッスン4:ゾンビのバトルシステム
payable修飾詞
- コントラクトがお金(Ether)を受け取れるようにする。
-
ether
という組み込み定数を使える。
-
- 例(Solidity側)
contract OnlineStore {
function buySomething() external payable {
// Check to make sure 0.001 ether was sent to the function call:
require(msg.value == 0.001 ether);
// If so, some logic to transfer the digital item to the caller of the function:
transferThing(msg.sender);
}
- 例(web3.js(DAppのJavaScriptフロントエンド)側)
// Assuming `OnlineStore` points to your contract on Ethereum:
OnlineStore.buySomething({from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001)})
transfer関数
- コントラクトから任意のアドレスにお金(Ether)を送る関数。Members of addressを参照。
-
this.balance
で、コントラクト内の残高を確認できる。
-
- ownerに作成者のアドレスが入っている場合、
owner.transfer(this.balance);
のように書けば、コントラクト内の全額をownerに送金できる。- 例えば、アイテムの購入時に支払いが多すぎた場合、お釣りを購入者に返金するには、
uint itemFee = 0.001 ether;
msg.sender.transfer(msg.value - itemFee);
乱数
- 完全な乱数を得ることはできない。
- keccak256ハッシュ関数を使って、nowやmsg.senderや自前のnonceを使って乱数を得ることはできるが、不誠実なノードがトランザクションを発行し、自分に有利な乱数になるまで他のノードに伝搬しないようにすれば、結果を操作できるため。
リファクタリング:ゾンビの持ち主であることを確認するmodifier
- 関数を呼び出したアカウントが、_zombieIdのゾンビのオーナーであるかのチェックは頻繁に使われるので、
modifier
にしてまとめる。
modifier ownerOf(uint _zombieId) {
require(msg.sender == zombieToOwner[_zombieId]);
_;
}
レッスン5:ERC721とクリプト収集物
(イーサリアム上の)トークンとは
-
基本的には、以下の2つを持つコントラクト。
- 誰がトークンをどれくらいを所有しているのかを記録する変数
- ユーザーが自分のトークンを他のアドレスに送ることができるようにする機能
ERC20トークンは、同じ名前で同じ関数を持っているので、ERC20に準拠した別な種類のトークンとやりとりできる。
このように、ERC20トークンは通貨のように使える。
CryptZombieで作ったゾンビのようなクリプト収集物(それぞれが異なっていて、分割できない物)には、ERC721トークンが適している。
規格に従うことにより、他の誰かがオークションや販売のプラットフォームを作れる。
多重継承
-
contract ZombieOwnership is ZombieAttack, ERC721 {
のように、継承元をカンマで区切って、複数のコントラクトを継承できる。
ERC721に準拠するための実装
残高関連
- balanceOf・・・addressを受け取り、そのアドレスのトークン量(保有するゾンビの数)を返す。
- ownerOf・・・トークンID(ゾンビID)を受け取り、その所有者のアドレスを返す。
- レッスン内では、同じ名前のmodifierを使っていたので、modifierの名前を変更した。
所有権の移転(トランスファー)
-
ERC721では2つのやり方がある。
- トークン所有者が
transfer
関数を使う方法。送り先のアドレスと、送りたいトークンIDを指定する。 - (1)トークン所有者が
approve
関数で送り先と送りたいトークンIDを指定し、コントラクトが受け取り許可リストに追加する。(2)受け取り側がtakeOwnership
を呼び出し、コントラクト側で許可リストにあるかチェックしてから受け取り側にトークンを移転する。
- トークン所有者が
上記のやり方で転送する際に、
Transfer
イベントも発生させる必要がある。このチャプターでは、トークン移転部分を_transfer関数として共通化している。
名前を変えたonlyOwnerOfモディファイヤーは、トークンIDを指定して、その所有者かどうかをチェックする関数。
コントラクトのセキュリティ強化
オーバーフローとアンダーフロー
- 例えば、uint8は0-255までの値しか格納できないので、255+1=0(オーバーフロー)で、0-1=255(アンダーフロー)
OpenZeppelinのSafeMathライブラリ
- OpenZeppelinのSafeMathライブラリは、無意識にオーバーフローやアンダーフローが起きた時にassertが発生するようになっている。
- safemathを使うには、importして、以下のように宣言する。
- データ型に対して、ライブラリ内の関数を使えるようにする宣言。
-
uint test=2; test = test.mul(3);
のように使える。
using safeMath for uint256;
-
require
とassert
の違いは、require
は偽の時に残りのgasをユーザに返却するが、assert
は返却しない。- 通常は
require
を使う。
- 通常は
- 上記では、uint256に対してのチェックなので、uint32などの小さい型に使うと正しいチェックにならない。safeMath32のように、型に合ったライブラリを使っているか注意する。
コメントの書き方の規則: natspecフォーマット
-
@title
,@author
は、タイトルと作者。 -
@notice
は、ユーザ向けの説明。コントラクトや関数が何をするのか説明する。 -
@dev
は、開発者向けの詳細な説明。 -
@param
,@return
は、引数パラメタと戻り値。
レッスン6:アプリのフロントエンドとWeb3.js
Web3.js とは
- 「Web3.js」・・・Ethereumのノード上にあるスマートコントラクトとやり取りするためのJavaScriptのライブラリ。
- 「Web3プロバイダ」・・・イーサリアムのどのノードにつなぐかの指定。
- 「Infura」・・・API経由でノードにキャッシュ付きでアクセスできるようにする無料サービス。
- InfuraをWeb3プロバイダとして使うには、
var web3 = new Web3(new Web3.providers.WebsocketProvider("wss://mainnet.infura.io/ws"));
Metamask(ブラウザ拡張)
- ChromeとFirefoxのブラウザ拡張機能で、イーサリアム・アカウントと秘密鍵を安全に管理できる。ブラウザがWeb3に対応するので、ブラウザからイーサリアムを使うウェブサイトと対話できるようになる。
- JavaScriptのグローバルオブジェクト
web3
にweb3プロバイダをインジェクションするので、Metamaskがインストール済みかのチェックに使える。
- JavaScriptのグローバルオブジェクト
window.addEventListener('load', function() {
// Web3がブラウザにインジェクトされているかチェック (Mist/MetaMask)
if (typeof web3 !== 'undefined') {
// Mist/MetaMaskのプロバイダの使用
web3js = new Web3(web3.currentProvider);
} else {
// ユーザーがweb3を持たない場合の対処。
// アプリを使用するためにMetamaskをインストールするよう
// 伝えるメッセージを表示。
}
// アプリのスタート&Web3.jsへの自由なアクセスが可能に:
startApp()
})
コントラクトへのアクセス
-
Web3.jsがコントラクトにアクセスするには、コントラクトのアドレスと、ABIが必要。
- コントラクトのアドレスは、イーサリアム上の固定アドレスで、デプロイ時に決まる。
- ABIは、コントラクトのメソッドをJSON形式で表したもの。Application Binary Interface。
上記の例では、
startApp()
でインスタンス化する
function startApp() {
var cryptoZombieAddress = "YOUR_CONTRACT_ADDRESS";
cryptZombies = new web3js.eth.Contract(cryptoZombieABI, cryptoZombieAddress);
}
コントラクトの関数呼び出し
call
-
call
は、view
関数およびpure
関数に使う。つまりブロックチェーンへの書き込みが無いので、ローカルのノードのみで完結する。
send
-
send
は、トランザクションを生成し、ブロックチェーン上のデータを変更する場合に使う。gas代がかかる。
myContract.methods.myMethod1(123).call()
myContract.methods.myMethod2(456).send()
- 書き方は、
コントラクト.methods.メソッド名(引数).call()
Web3.jsのバージョン1.0で、書き方が大きく変わった
- 「コールバックの代わりにPromiseを使う」
- Promise・・・特定のコードの実行を待ってから、次のコードを実行するための仕組み。
-
then
を使って、いくつもの非同期操作をチェインできる。
Metamaskでユーザを切り替えた時の対処
- Metamaskは複数のアカウントを使用できる。
- 現在どのアカウントが使用されているかは、web3上の変数で確認できる。
var userAccount = web3.eth.accounts[0]
- ユーザのページを表示している時に、Metamaskのアカウントを切り替えたら、所有しているゾンビも切り替えたい。
- JavaScriptの
setInterval
で一定間隔ごとに監視し、切り替わっていたら表示をアップデートする。
- JavaScriptの
ゾンビ軍団の表示
- jQueryで表示する
- index.htmlには、「
<div id="zombies"></div>
」が書かれている。 - ゾンビIDが配列で渡されるので、
- #zombies divの中身を消す
- 各ゾンビIDごとに、
getZombieDetails(id)
でゾンビの情報を呼び出し、HTMLに整形して、#zombies divに追加する。
トランザクションの送信
- コントラクト上のデータを変更するので、
send
関数を使う。 - 関数を呼び出すユーザのアドレスが必要。
- gas代がかかる。
- トランザクションをsendしてから、ブロックチェーン状で使えるようになるには、遅れがある。
- ブロックに取り込まれるまでに時間があるから。
- そのため、非同期処理が必要。
- トランザクションがブロックに取り込まれると、
receipt
が返ってくる。-
.on("receipt", function(receipt) {
のように、結果をハンドリングする。
-
- gas不足などで、トランザクションがブロックに取り込まれない場合は、
error
が返ってくる。-
.on("error"), function(error) {
のように、エラーもハンドリングする。
-
payable関数の呼び出し
- JavaScriptからいくら送信するかを、Etherではなくweiで指定する必要がある。
- weiとはEtherの最小単位で、1 etherには10^18 weiがある。
// これが1 ETHをWeiに変換してくれる
web3js.utils.toWei("1", "ether");
イベントのサブスクライブ(リッスンしてconsole.logを表示)
- 例
cryptoZombies.event.NewZombie()
.on("data", function(event) {
let zombie = event.returnValues;
console.log("A new zombie was born!");
})
.on("error", console.error);