CryptoZombiesをさらに触ってみる

前回に引き続き勉強のためCryptoZombiesを触っていこうと思います。

前回(レッスン1~レッスン3)

レッスン4

CryptoZombies_lesson4

修飾詞

いつどこで関数を呼び出すかをコントロールする可視性修飾詞というものがある。private修飾詞はコントラクト内の別の関数からのみ呼び出されるという意味だ。internal修飾詞はprivate修飾詞に似ているが、そのコントラクトを継承したコントラクトからも呼び出す事ができる。external修飾詞はコントラクト外からだけ呼び出す事ができて、最後にpublic修飾詞だが、これはコントラクト内部・外部どちらからでも呼び出せるぞ。

コントラクトコードの理解をしておかないと、セキュリティリスクがあります。
ここはしっかり把握しておきます。

payable修飾詞

payable関数は、solidityとEthereumをこんなにもクールにしているものの1つといえる。Etherを受け取ることができる特別なタイプの関数なんだ。

だがイーサリアムでは、お金(Ether)もデータ(トランザクションの内容)も、コントラクト・コード自体も全てイーサリアム上にあるから、ファンクション・コール及びお金の支払いが同時に可能だ。

関数を実行するため、コントラクトへいくらかの支払いを要求するというようなすごく面白いこともできてしまうのだ。

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);
  }
}

特に他のライブラリーとか入れなくても、最初から支払いが実装されているのはとてもいいですね!
それが、DAppsのいいところか…

ここのmsg.valueは、コントラクトにどのくらいEtherが送られたかを見るやり方で、etherは組み込み単位だ。

ブロックチェーンの全内容が全ての参加者に見えているため、これは難しい問題である。

注: ここで思い出して欲しい。構造体内部にuintは格納可能であるが、使用するuintは最小に済ませたいのだ。uint8だと2の8乗は256なので、もし一日一回ゾンビが攻撃をした場合は一年以内にオーバーフローとなり得る。だが2の16乗は65536なので、ユーザーが179年にわたって毎日勝つか負けるかしない限り、これで大丈夫だ。

なるほど…

レッスン5

CryptoZombies_lesson5

お主がイーサリアムの界隈に足を踏み入れたことがあるなら、トークン、特に ERC20トークン が話題になってるのを恐らく聞いたことがあるだろう。

イーサリアム上のトークン は、基本的にいくつかの共通ルールに従ったスマート・コントラクトだ。具体的に言うと、transfer(address _to, uint256 _value) や balanceOf(address _owner) といった関数のスタンダードセットを実装しているものだ。

つまり基本的には、トークンとは、誰がトークンをどれくらいを所有しているのかを記録するコントラクトと、ユーザーが自分のトークンを他のアドレスに送ることができるようにする機能のことなのだ。

注: ERC721のような標準を使用するメリットとして、オークション及びプレイヤーがゾンビをトレード/販売するやり方を決定するエスクロー・ロジックをコントラクト内で実装する必要がなくなるという点があります。仕様に準拠すれば、他の誰かが収集可能なERC721クリプト資産の交換プラットフォームを作ることができ、私たちのERC721ゾンビはそのプラットフォームで使用できるようになります。そのため、独自の取引ロジックを展開する代わりにトークン規格を使用するのは明らかにメリットがあります。

この辺、理解しやすい…

このコードをコンパイルしようとしても、コンパイラは同じ名前の修飾詞と関数を持つことはできないとエラーを出す。

function transfer(address _to, uint256 _tokenId) public;
function approve(address _to, uint256 _tokenId) public;
function takeOwnership(uint256 _tokenId) public;

一番目の方法はトークン所有者が送り先のaddress、そして送りたいトークンの_tokenIdを送ってtransfer関数を呼び出すものだ。

二番目の方法は、トークン所有者がまずapprove関数を呼び出し、一番目と同じ情報を関数に送る。すると、コントラクトが誰がトークン受け取りを許可されたのかを、通常はmapping (uint256 => address)にて記録する。さらに誰かがtakeOwnershipを呼び出すと、コントラクトはそのmsg.senderがトークンを受け取ることを所有者から承認されているかをチェックし、承認済みの場合は彼にトークンを移転する。

ERC721をオーバーライドする感覚ですね。

assertはrequireと同じようなものだが、偽の場合はエラーを投げる。 assertとrequireの違いは、requireは関数呼び出しが失敗した場合にユーザーにガスの残りを返却するが、このときassertはそうしない。 なのでコード中ではほとんどrequireを使いたい。assertはコードにひどい間違いがおこった場合に一般的に使用される(uintのオーバーフローのようにである)。
つまりuint16とuint32でオーバーフロー/アンダーフローを回避するには、さらに2つのライブラリを実装することが必要なのだ。それらライブラリを、SafeMath16、SafeMath32と呼ぼう。

SafeMathは多くのDAppsで導入されているので、もう使うものと思っておきます。

コメントアウト

Solidityのコミュニティでは、 natspec というフォーマットを用いることがスタンダードとなっている。

@devとかよくみるのは、natspecのフォーマットだったんですね。

レッスン6

CryptoZombies_lesson6

レッスン6だけ、まだ日本語訳がないので英語でやりました。

  1. The address of the smart contract
  2. The function you want to call, and
  3. The variables you want to pass to that function.

この3段階でブラウザからスマートコントラクトを呼び出せます。
イーサリアムのnodeJSON-RPCで呼ばなければいけません。

// Yeah... Good luck writing all your function calls this way!
// Scroll right ==>
{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{"from":"0xb60e8dd61c5d32be8058bb8eb970870f07233155","to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","gas":"0x76c0","gasPrice":"0x9184e72a000","value":"0x9184e72a","data":"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}],"id":1}

web3

ただ、こんなコード書くの嫌だ…
そこで登場するのがweb3.js
ありがとう!

CryptoZombies.methods.createRandomZombie("Vitalik Nakamoto ")
  .send({ from: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", gas: "3000000" })

Infura is a service that maintains a set of Ethereum nodes with a caching layer for fast reads, which you can access for free through their API. Using Infura as a provider, you can reliably send and receive messages to/from the Ethereum blockchain without needing to set up and maintain your own node.

But luckily you don’t need to — there are already services that handle this for you. The most popular of these is

Metamaskを使うことで、秘密鍵管理しなくてもいいDAppsを構築することができます。
web3からMetaMaskのアカウントを確認できます。

Here’s some template code provided by Metamask for how we can detect to see if the user has Metamask installed, and if not tell them they’ll need to install it to use our app:

window.addEventListener('load', function() {

  // Checking if Web3 has been injected by the browser (Mist/MetaMask)
  if (typeof web3 !== 'undefined') {
    // Use Mist/MetaMask's provider
    web3js = new Web3(web3.currentProvider);
  } else {
    // Handle the case where the user doesn't have web3. Probably 
    // show them a message telling them to install Metamask in 
    // order to use our app.
  }

  // Now you can start your app & access web3js freely:
  startApp()

})

Web3.js will need 2 things to talk to your contract: its address and its ABI.
ABI stands for Application Binary Interface.

When you compile your contract to deploy to Ethereum (which we’ll cover in Lesson 7), the Solidity compiler will give you the ABI, so you’ll need to copy and save this in addition to the contract address.

Once you have your contract’s address and ABI, you can instantiate it in Web3 as follows:

web3のインスタンス化をするためには、addressABIがあれば大丈夫です。

// Instantiate myContract
var myContract = new web3js.eth.Contract(myABI, myContractAddress);

call & send

Web3.js has two methods we will use to call functions on our contract: call and send.

call is used for view and pure functions. It only runs on the local node, and won’t create a transaction on the blockchain.

send will create a transaction and change data on the blockchain. You’ll need to use send for any functions that aren’t view or pure.

callsendでnodeとやりとりをするんですが、callはブロックチェーンに何も書き込みません。
一方sendはブロックチェーンに書き込みます。

Recall that we made our array of zombies public:

Zombie[] public zombies;

We can see which account is currently active on the injected web3 variable via:

var userAccount = web3.eth.accounts[0]

MetaMaskのアカウントを確認できます。

var accountInterval = setInterval(function() {
  // Check if account has changed
  if (web3.eth.accounts[0] !== userAccount) {
    userAccount = web3.eth.accounts[0];
    // Call some function to update the UI with the new account
    updateInterface();
  }
}, 100);

What this does is check every 100 milliseconds to see if userAccount is still equal web3.eth.accounts[0] (i.e. does the user still have that account active). If not, it reassigns userAccount to the currently active account, and calls a function to update the display.

If you want to see our exact implementation, we’ve open sourced the Vue.js component we use for the zombie’s appearance, which you can view here.
https://github.com/loomnetwork/zombie-char-component

function createRandomZombie(name) {
  // This is going to take a while, so update the UI to let the user know
  // the transaction has been sent
  $("#txStatus").text("Creating new zombie on the blockchain. This may take a while...");
  // Send the tx to our contract:
  return CryptoZombies.methods.createRandomZombie(name)
  .send({ from: userAccount })
  .on("receipt", function(receipt) {
    $("#txStatus").text("Successfully created " + name + "!");
    // Transaction was accepted into the blockchain, let's redraw the UI
    getZombiesByOwner(userAccount).then(displayZombies);
  })
  .on("error", function(error) {
    // Do something to alert the user their transaction has failed
    $("#txStatus").text(error);
  });
}

コントラクトコードのメソッドを実行すると、receipterrorが帰ってくるんですね。

eventの予約

Because you can use this method to query the event logs since the beginning of time, this presents an interesting use case: Using events as a cheaper form of storage.

If you recall, saving data to the blockchain is one of the most expensive operations in Solidity. But using events is much much cheaper in terms of gas.

The tradeoff here is that events are not readable from inside the smart contract itself. But it’s an important use-case to keep in mind if you have some data you want to be historically recorded on the blockchain so you can read it from your app’s front-end.

eventの方がgasが安いらしい。

The syntax we just described above is from the latest 1.0 release of Web3.js, which uses WebSockets to subscribe to events.

However, MetaMask doesn’t yet support the latest events API (although they’re actively working on it — check this github issue for updates).

So for now we’ll have to use a separate Web3 provider that supports WebSockets specifically for the events. We can use Infura to instantiate a second copy as follows:

var web3Infura = new Web3(new Web3.providers.WebsocketProvider("wss://mainnet.infura.io/ws"));
var czEvents = new web3Infura.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress);

eventはまだMetaMaskで使えないので、上記のようにczEventsを呼び出します。

Then we would use czEvents.events.Transfer to listen to events instead of cryptoZombies.events.Transfer. We would still use cryptoZombies.methods for everything else we’ve covered in this lesson.

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です